導(dǎo)語(yǔ):作為java領(lǐng)域最受歡迎的任務(wù)調(diào)度庫(kù)之一,quartz為開(kāi)發(fā)者提供了豐富的任務(wù)調(diào)度功能,比如讓某段程序在每天18:00準(zhǔn)時(shí)執(zhí)行。本文將通過(guò)demo和源碼,講解quartz如何使用、主要功能有哪些、原理是什么,并挑選幾段有用的源碼片段進(jìn)行解讀。
1、quartz簡(jiǎn)介
quartz,即石英的意思,隱喻如石英表般對(duì)時(shí)間的準(zhǔn)確把握。
quartz是一個(gè)由java編寫(xiě)的任務(wù)調(diào)度庫(kù),由OpenSymphony組織開(kāi)源出來(lái)。那么問(wèn)題來(lái)了,任務(wù)調(diào)度是個(gè)什么東西?舉個(gè)栗子,現(xiàn)在有N個(gè)任務(wù)(程序),要求在指定時(shí)間執(zhí)行,比如每周二3點(diǎn)執(zhí)行任務(wù)A、每天相隔5s執(zhí)行任務(wù)B等等,這種多任務(wù)擁有多種執(zhí)行策略就是任務(wù)調(diào)度。而quartz的核心作用,是使任務(wù)調(diào)度變得豐富、高效、安全,開(kāi)發(fā)者只需要調(diào)幾個(gè)quartz接口并做簡(jiǎn)單配置,即可實(shí)現(xiàn)上述需求。
quartz號(hào)稱(chēng)能夠同時(shí)對(duì)上萬(wàn)個(gè)任務(wù)進(jìn)行調(diào)度,擁有豐富的功能特性,包括任務(wù)調(diào)度、任務(wù)持久化、可集群化、插件等。目前最新版本是2.2.3,從github[1]上看,2.2.4已在開(kāi)發(fā)中。
quartz有競(jìng)品嗎?有,那就是java Timer。quartz相對(duì)于java Timer的優(yōu)勢(shì)包括任務(wù)持久化、配置更豐富、線(xiàn)程池等等,詳見(jiàn)官網(wǎng)[2]解釋,兩者在Spring上的用法可見(jiàn)這篇文章[^OpenSymphony Quartz和java Timer]。
接下來(lái),筆者將從一個(gè)簡(jiǎn)單的demo開(kāi)始,順著demo里使用到的quartz接口,逐個(gè)分析quartz主要功能及其原理。限于篇幅,demo中未涉及的功能,本文不涉及,比如集群化等。最后,挑選2段對(duì)大家日常開(kāi)發(fā)有用的源碼進(jìn)行解讀。
讀這篇文章有什么用
- 對(duì)一個(gè)任務(wù)調(diào)度系統(tǒng)產(chǎn)生初步的原理級(jí)了解
- 更正確地使用quartz
- 學(xué)到如何采用多線(xiàn)程進(jìn)行任務(wù)調(diào)度的源碼
- 學(xué)到如何避免GC的源碼
- 精(xian)力(de)充(dan)沛(teng),隨便找篇技術(shù)文讀讀
[^OpenSymphony Quartz和java Timer]: yaerfeng:Spring定時(shí)器配置的兩種實(shí)現(xiàn)方式OpenSymphony Quartz和java Timer詳解
2、使用代碼demo[3]
本文的demo程序由2個(gè)java文件和quartz.properties組成,quartz.properties是可選的,因?yàn)閝uartz有默認(rèn)配置。demo實(shí)現(xiàn)從當(dāng)前時(shí)間開(kāi)始,每隔2s執(zhí)行一次JobImpl類(lèi)的execute()方法。
- TestQuartz.java
/**
* 從當(dāng)前時(shí)間開(kāi)始,每隔2s執(zhí)行一次JobImpl#execute()
* @author hlx
*/
public class TestQuartz {
public static void main(String[] args) throws SchedulerException, InterruptedException {
// 創(chuàng)建調(diào)度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 創(chuàng)建任務(wù)
JobDetail jobDetail = JobBuilder.newJob(JobImpl.class).withIdentity("myJob", "jobGroup").build();
// 創(chuàng)建觸發(fā)器
// withIntervalInSeconds(2)表示每隔2s執(zhí)行任務(wù)
Date triggerDate = new Date();
SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity("myTrigger", "triggerGroup");
Trigger trigger = triggerBuilder.startAt(triggerDate).withSchedule(schedBuilder).build();
// 將任務(wù)及其觸發(fā)器放入調(diào)度器
scheduler.scheduleJob(jobDetail, trigger);
// 調(diào)度器開(kāi)始調(diào)度任務(wù)
scheduler.start();
}
}
- JobImpl.java
/**
* @author hlx
*/
public class JobImpl implements Job {
public void execute(JobExecutionContext context) {
System.out.println("job impl running");
}
}
- quartz.properties(可選)
#調(diào)度器名,默認(rèn)名是QuartzScheduler
org.quartz.scheduler.instanceName: TestQuartzScheduler
#============================================================================
# Configure ThreadPool 配置線(xiàn)程池
#============================================================================
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
#============================================================================
# Configure JobStore 配置任務(wù)存儲(chǔ)方式
#============================================================================
#相當(dāng)于掃描頻率
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
我們順著注釋看到,TestQuartz.main()
依次創(chuàng)建了scheduler(調(diào)度器)、job(任務(wù))、trigger(觸發(fā)器),其中,job指定了JobImpl,trigger保存job的觸發(fā)執(zhí)行策略(每隔2s執(zhí)行一次),scheduler將job和trigger綁定在一起,最后scheduler.start()
啟動(dòng)調(diào)度,每隔2s觸發(fā)執(zhí)行JobImpl.execute()
,打印出job impl running
。
對(duì)于quartz.properties,簡(jiǎn)單場(chǎng)景下,開(kāi)發(fā)者不用自定義配置,使用quartz默認(rèn)配置即可,但在要求較高的使用場(chǎng)景中還是要自定義配置,比如通過(guò)org.quartz.threadPool.threadCount
設(shè)置足夠的線(xiàn)程數(shù)可提高多job場(chǎng)景下的運(yùn)行性能。更詳盡的配置見(jiàn)官網(wǎng)配置說(shuō)明頁(yè)。
如果讓我們?cè)O(shè)計(jì)一個(gè)任務(wù)調(diào)度系統(tǒng),會(huì)像quartz那樣將job、trigger、scheduler解藕?jiǎn)幔縬uartz這樣設(shè)計(jì)的原因,筆者認(rèn)為有兩點(diǎn):
- job與trigger解藕,其實(shí)就是將任務(wù)本身和任務(wù)執(zhí)行策略解藕,這樣可以方便實(shí)現(xiàn)N個(gè)任務(wù)和M個(gè)執(zhí)行策略自由組合,比較容易理解;
- scheduler單獨(dú)分離出來(lái),相當(dāng)于一個(gè)指揮官,可以從全局做調(diào)度,比如監(jiān)聽(tīng)哪些trigger已經(jīng)ready、分配線(xiàn)程等等,如果沒(méi)有scheduler,則trigger間會(huì)競(jìng)爭(zhēng)混亂,難以實(shí)現(xiàn)諸如trigger優(yōu)先級(jí)等功能,也無(wú)法合理使用資源。
下面,筆者將分別就job、trigger、scheduler進(jìn)行原理分析。
3、job(任務(wù))
job由若干個(gè)class
和interface
實(shí)現(xiàn)。
Job接口
開(kāi)發(fā)者想要job完成什么樣的功能,必須且只能由開(kāi)發(fā)者自己動(dòng)手來(lái)編寫(xiě)實(shí)現(xiàn),比如demo中的JobImpl
,這點(diǎn)無(wú)容置疑。但要想讓自己的job被quartz識(shí)別,就必須按照quartz的規(guī)則來(lái)辦事,這個(gè)規(guī)則就是job實(shí)現(xiàn)類(lèi)必須實(shí)現(xiàn)Job接口,比如JobImpl
就實(shí)現(xiàn)了Job
。
Job
只有一個(gè)execute(JobExecutionContext)
,JobExecutionContext
保存了job的上下文信息,比如綁定的是哪個(gè)trigger。job實(shí)現(xiàn)類(lèi)必須重寫(xiě)execute()
,執(zhí)行job實(shí)際上就是運(yùn)行execute()
。
JobDetailImpl類(lèi) / JobDetail接口
JobDetailImpl類(lèi)
實(shí)現(xiàn)了JobDetail接口
,用來(lái)描述一個(gè)job,定義了job所有屬性及其get/set方法。了解job擁有哪些屬性,就能知道quartz能提供什么樣的能力,下面筆者用表格列出job若干核心屬性。
屬性名 | 說(shuō)明 |
---|---|
class | 必須是job實(shí)現(xiàn)類(lèi)(比如JobImpl ),用來(lái)綁定一個(gè)具體job |
name | job名稱(chēng)。如果未指定,會(huì)自動(dòng)分配一個(gè)唯一名稱(chēng)。所有job都必須擁有一個(gè)唯一name,如果兩個(gè)job的name重復(fù),則只有最前面的job能被調(diào)度 |
group | job所屬的組名 |
description | job描述 |
durability | 是否持久化。如果job設(shè)置為非持久,當(dāng)沒(méi)有活躍的trigger與之關(guān)聯(lián)的時(shí)候,job會(huì)自動(dòng)從scheduler中刪除。也就是說(shuō),非持久job的生命期是由trigger的存在與否決定的 |
shouldRecover | 是否可恢復(fù)。如果job設(shè)置為可恢復(fù),一旦job執(zhí)行時(shí)scheduler發(fā)生hard shutdown(比如進(jìn)程崩潰或關(guān)機(jī)),當(dāng)scheduler重啟后,該job會(huì)被重新執(zhí)行 |
jobDataMap | 除了上面常規(guī)屬性外,用戶(hù)可以把任意kv數(shù)據(jù)存入jobDataMap,實(shí)現(xiàn)job屬性的無(wú)限制擴(kuò)展,執(zhí)行job時(shí)可以使用這些屬性數(shù)據(jù)。此屬性的類(lèi)型是JobDataMap ,實(shí)現(xiàn)了Serializable接口 ,可做跨平臺(tái)的序列化傳輸 |
JobBuilder類(lèi)
// 創(chuàng)建任務(wù)
JobDetail jobDetail = JobBuilder.newJob(JobImpl.class).withIdentity("myJob", "jobGroup").build();
上面代碼是demo一個(gè)片段,可以看出JobBuilder類(lèi)
的作用:接收job實(shí)現(xiàn)類(lèi)JobImpl
,生成JobDetail
實(shí)例,默認(rèn)生成JobDetailImpl
實(shí)例。
這里運(yùn)用了建造者模式:JobImpl
相當(dāng)于Product;JobDetail
相當(dāng)于Builder,擁有job的各種屬性及其get/set方法;JobBuilder
相當(dāng)于Director,可為一個(gè)job組裝各種屬性。
4、trigger(觸發(fā)器)
trigger由若干個(gè)class
和interface
實(shí)現(xiàn)。
SimpleTriggerImpl類(lèi) / SimpleTrigger接口 / Trigger接口
SimpleTriggerImpl類(lèi)
實(shí)現(xiàn)了SimpleTrigger接口
,SimpleTrigger接口
繼承了Trigger接口
,它們表示觸發(fā)器,用來(lái)保存觸發(fā)job的策略,比如每隔幾秒觸發(fā)job。實(shí)際上,quartz有兩大觸發(fā)器:SimpleTrigger
和CronTrigger
,限于篇幅,本文僅介紹SimpleTrigger
。
Trigger諸類(lèi)保存了trigger所有屬性,同job屬性一樣,了解trigger屬性有助于我們了解quartz能提供什么樣的能力,下面筆者用表格列出trigger若干核心屬性。
在quartz源碼或注釋中,經(jīng)常使用fire(點(diǎn)火)這個(gè)動(dòng)詞來(lái)命名屬性名,表示觸發(fā)job。
屬性名 | 屬性類(lèi)型 | 說(shuō)明 |
---|---|---|
name | 所有trigger通用 | trigger名稱(chēng) |
group | 所有trigger通用 | trigger所屬的組名 |
description | 所有trigger通用 | trigger描述 |
calendarName | 所有trigger通用 | 日歷名稱(chēng),指定使用哪個(gè)Calendar類(lèi),經(jīng)常用來(lái)從trigger的調(diào)度計(jì)劃中排除某些時(shí)間段 |
misfireInstruction | 所有trigger通用 | 錯(cuò)過(guò)job(未在指定時(shí)間執(zhí)行的job)的處理策略,默認(rèn)為MISFIRE_INSTRUCTION_SMART_POLICY 。詳見(jiàn)這篇blog^Quartz misfire
|
priority | 所有trigger通用 | 優(yōu)先級(jí),默認(rèn)為5 。當(dāng)多個(gè)trigger同時(shí)觸發(fā)job時(shí),線(xiàn)程池可能不夠用,此時(shí)根據(jù)優(yōu)先級(jí)來(lái)決定誰(shuí)先觸發(fā) |
jobDataMap | 所有trigger通用 | 同job的jobDataMap。假如job和trigger的jobDataMap有同名key,通過(guò)getMergedJobDataMap() 獲取的jobDataMap,將以trigger的為準(zhǔn) |
startTime | 所有trigger通用 | 觸發(fā)開(kāi)始時(shí)間,默認(rèn)為當(dāng)前時(shí)間。決定什么時(shí)間開(kāi)始觸發(fā)job |
endTime | 所有trigger通用 | 觸發(fā)結(jié)束時(shí)間。決定什么時(shí)間停止觸發(fā)job |
nextFireTime | SimpleTrigger私有 | 下一次觸發(fā)job的時(shí)間 |
previousFireTime | SimpleTrigger私有 | 上一次觸發(fā)job的時(shí)間 |
repeatCount | SimpleTrigger私有 | 需觸發(fā)的總次數(shù) |
timesTriggered | SimpleTrigger私有 | 已經(jīng)觸發(fā)過(guò)的次數(shù) |
repeatInterval | SimpleTrigger私有 | 觸發(fā)間隔時(shí)間 |
TriggerBuilder類(lèi)
// 創(chuàng)建觸發(fā)器
// withIntervalInSeconds(2)表示每隔2s執(zhí)行任務(wù)
Date triggerDate = new Date();
SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity("myTrigger", "triggerGroup");
Trigger trigger = triggerBuilder.startAt(triggerDate).withSchedule(schedBuilder).build();
上面代碼是demo一個(gè)片段,可以看出TriggerBuilder類(lèi)
的作用:生成Trigger實(shí)例,默認(rèn)生成SimpleTriggerImpl
實(shí)例。同JobBuilder
一樣,這里也運(yùn)用了建造者模式。
5、scheduler(調(diào)度器)
scheduler主要由StdScheduler類(lèi)
、Scheduler接口
、StdSchedulerFactory類(lèi)
、SchedulerFactory接口
、QuartzScheduler類(lèi)
實(shí)現(xiàn),它們的關(guān)系見(jiàn)下面UML圖。
// 創(chuàng)建調(diào)度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
......
// 將任務(wù)及其觸發(fā)器放入調(diào)度器
scheduler.scheduleJob(jobDetail, trigger);
// 調(diào)度器開(kāi)始調(diào)度任務(wù)
scheduler.start();
上面代碼是demo一個(gè)片段,可以看出這里運(yùn)用了工廠(chǎng)模式,通過(guò)factory類(lèi)(StdSchedulerFactory
)生產(chǎn)出scheduler實(shí)例(StdScheduler
)。scheduler是整個(gè)quartz的關(guān)鍵,為此,筆者把demo中用到的scheduler接口的源碼加上中文注釋做個(gè)講解。
- StdSchedulerFactory.getScheduler()源碼
public Scheduler getScheduler() throws SchedulerException {
// 讀取quartz配置文件,未指定則順序遍歷各個(gè)path下的quartz.properties文件
// 解析出quartz配置內(nèi)容和環(huán)境變量,存入PropertiesParser對(duì)象
// PropertiesParser組合了Properties(繼承Hashtable),定義了一系列對(duì)Properties的操作方法,比如getPropertyGroup()批量獲取相同前綴的配置。配置內(nèi)容和環(huán)境變量存放在Properties成員變量中
if (cfg == null) {
initialize();
}
// 獲取調(diào)度器池,采用了單例模式
// 其實(shí),調(diào)度器池的核心變量就是一個(gè)hashmap,每個(gè)元素key是scheduler名,value是scheduler實(shí)例
// getInstance()用synchronized防止并發(fā)創(chuàng)建
SchedulerRepository schedRep = SchedulerRepository.getInstance();
// 從調(diào)度器池中取出當(dāng)前配置所用的調(diào)度器
Scheduler sched = schedRep.lookup(getSchedulerName());
......
// 如果調(diào)度器池中沒(méi)有當(dāng)前配置的調(diào)度器,則實(shí)例化一個(gè)調(diào)度器,主要?jiǎng)幼靼ǎ? // 1)初始化threadPool(線(xiàn)程池):開(kāi)發(fā)者可以通過(guò)org.quartz.threadPool.class配置指定使用哪個(gè)線(xiàn)程池類(lèi),比如SimpleThreadPool。先class load線(xiàn)程池類(lèi),接著動(dòng)態(tài)生成線(xiàn)程池實(shí)例bean,然后通過(guò)反射,使用setXXX()方法將以org.quartz.threadPool開(kāi)頭的配置內(nèi)容賦值給bean成員變量;
// 2)初始化jobStore(任務(wù)存儲(chǔ)方式):開(kāi)發(fā)者可以通過(guò)org.quartz.jobStore.class配置指定使用哪個(gè)任務(wù)存儲(chǔ)類(lèi),比如RAMJobStore。先class load任務(wù)存儲(chǔ)類(lèi),接著動(dòng)態(tài)生成實(shí)例bean,然后通過(guò)反射,使用setXXX()方法將以org.quartz.jobStore開(kāi)頭的配置內(nèi)容賦值給bean成員變量;
// 3)初始化dataSource(數(shù)據(jù)源):開(kāi)發(fā)者可以通過(guò)org.quartz.dataSource配置指定數(shù)據(jù)源詳情,比如哪個(gè)數(shù)據(jù)庫(kù)、賬號(hào)、密碼等。jobStore要指定為JDBCJobStore,dataSource才會(huì)有效;
// 4)初始化其他配置:包括SchedulerPlugins、JobListeners、TriggerListeners等;
// 5)初始化threadExecutor(線(xiàn)程執(zhí)行器):默認(rèn)為DefaultThreadExecutor;
// 6)創(chuàng)建工作線(xiàn)程:根據(jù)配置創(chuàng)建N個(gè)工作thread,執(zhí)行start()啟動(dòng)thread,并將N個(gè)thread順序add進(jìn)threadPool實(shí)例的空閑線(xiàn)程列表availWorkers中;
// 7)創(chuàng)建調(diào)度器線(xiàn)程:創(chuàng)建QuartzSchedulerThread實(shí)例,并通過(guò)threadExecutor.execute(實(shí)例)啟動(dòng)調(diào)度器線(xiàn)程;
// 8)創(chuàng)建調(diào)度器:創(chuàng)建StdScheduler實(shí)例,將上面所有配置和引用組合進(jìn)實(shí)例中,并將實(shí)例存入調(diào)度器池中
sched = instantiate();
return sched;
}
上面有個(gè)過(guò)程是初始化jobStore,表示使用哪種方式存儲(chǔ)scheduler相關(guān)數(shù)據(jù)。quartz有兩大jobStore:RAMJobStore
和JDBCJobStore
。RAMJobStore
把數(shù)據(jù)存入內(nèi)存,性能最高,配置也簡(jiǎn)單,但缺點(diǎn)是系統(tǒng)掛了難以恢復(fù)數(shù)據(jù)。JDBCJobStore
保存數(shù)據(jù)到數(shù)據(jù)庫(kù),保證數(shù)據(jù)的可恢復(fù)性,但性能較差且配置復(fù)雜。
- QuartzScheduler.scheduleJob(JobDetail, Trigger)源碼
public Date scheduleJob(JobDetail jobDetail,
Trigger trigger) throws SchedulerException {
// 檢查調(diào)度器是否開(kāi)啟,如果關(guān)閉則throw異常到上層
validateState();
......
// 獲取trigger首次觸發(fā)job的時(shí)間,以此時(shí)間為起點(diǎn),每隔一段指定的時(shí)間觸發(fā)job
Date ft = trig.computeFirstFireTime(cal);
if (ft == null) {
throw new SchedulerException(
"Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire.");
}
// 把job和trigger注冊(cè)進(jìn)調(diào)度器的jobStore
resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
// 通知job監(jiān)聽(tīng)者
notifySchedulerListenersJobAdded(jobDetail);
// 通知調(diào)度器線(xiàn)程
notifySchedulerThread(trigger.getNextFireTime().getTime());
// 通知trigger監(jiān)聽(tīng)者
notifySchedulerListenersSchduled(trigger);
return ft;
}
- QuartzScheduler.start()源碼
public void start() throws SchedulerException {
......
// 這句最關(guān)鍵,作用是使調(diào)度器線(xiàn)程跳出一個(gè)無(wú)限循環(huán),開(kāi)始輪詢(xún)所有trigger觸發(fā)job
// 原理詳見(jiàn)“如何采用多線(xiàn)程進(jìn)行任務(wù)調(diào)度”
schedThread.togglePause(false);
......
}
6、quartz線(xiàn)程模型
上圖是筆者在eclipse中調(diào)試demo時(shí)的線(xiàn)程圖,可以見(jiàn)到,除了第一條主線(xiàn)程外,還有10條工作線(xiàn)程,和1條調(diào)度器線(xiàn)程。
工作線(xiàn)程以{instanceName}_Worker-{[1-10]}
命名。線(xiàn)程數(shù)目由quart.properties文件中的org.quartz.threadPool.threadCount
配置項(xiàng)指定。所有工作線(xiàn)程都會(huì)放在線(xiàn)程池中,即所有工作線(xiàn)程都放在SimpleThreadPool
實(shí)例的一個(gè)LinkedList<WorkerThread>成員變量中。WorkerThread
是SimpleThreadPool
的內(nèi)部類(lèi),這么設(shè)計(jì)可能是因?yàn)椴幌肜^承SimpleThreadPool
但又想調(diào)用其protected方法,或者想隱藏WorkerThread
。線(xiàn)程池還擁有兩個(gè)LinkedList<WorkerThread>:availWorkers
和busyWorkers
,分別存放空閑和正在執(zhí)行job的工作線(xiàn)程。
調(diào)度器線(xiàn)程以{instanceName}_QuartzSchedulerThread
命名。該線(xiàn)程將根據(jù)trigger找出要待運(yùn)行job,然后從threadpool中拿出工作線(xiàn)程來(lái)執(zhí)行。調(diào)度器線(xiàn)程主體是QuartzSchedulerThread
對(duì)象。
{instanceName}
指的是quart.properties文件中的org.quartz.scheduler.instanceName
配置值,這里是TestQuartzScheduler
。[1-10]
表示從1到10的任意數(shù)字。
7、精彩源碼解讀
本節(jié)中,筆者從quartz源碼中挑選了兩段代碼,之所以選擇這兩段代碼,是因?yàn)樗鼈儗?shí)現(xiàn)了線(xiàn)程間通信、加鎖同步、避免GC等功能,對(duì)工程師們很有幫助。
如何采用多線(xiàn)程進(jìn)行任務(wù)調(diào)度
- QuartzSchedulerThread.java
// 調(diào)度器線(xiàn)程一旦啟動(dòng),將一直運(yùn)行此方法
public void run() {
......
// while()無(wú)限循環(huán),每次循環(huán)取出時(shí)間將到的trigger,觸發(fā)對(duì)應(yīng)的job,直到調(diào)度器線(xiàn)程被關(guān)閉
// halted是一個(gè)AtomicBoolean類(lèi)變量,有個(gè)volatile int變量value,其get()方法僅僅簡(jiǎn)單的一句return value != 0,get()返回結(jié)果表示調(diào)度器線(xiàn)程是否開(kāi)關(guān)
// volatile修飾的變量,存取必須走內(nèi)存,不能通過(guò)cpu緩存,這樣一來(lái)get總能獲得set的最新真實(shí)值,因此volatile變量適合用來(lái)存放簡(jiǎn)單的狀態(tài)信息
// 顧名思義,AtomicBoolean要解決原子性問(wèn)題,但volatile并不能保證原子性,詳見(jiàn)http://blog.csdn.net/wxwzy738/article/details/43238089
while (!halted.get()) {
try {
// check if we're supposed to pause...
// sigLock是個(gè)Object對(duì)象,被用于加鎖同步
// 需要用到wait(),必須加到synchronized塊內(nèi)
synchronized (sigLock) {
while (paused && !halted.get()) {
try {
// wait until togglePause(false) is called...
// 這里會(huì)不斷循環(huán)等待,直到QuartzScheduler.start()調(diào)用了togglePause(false)
// 調(diào)用wait(),調(diào)度器線(xiàn)程進(jìn)入休眠狀態(tài),同時(shí)sigLock鎖被釋放
// togglePause(false)獲得sigLock鎖,將paused置為false,使調(diào)度器線(xiàn)程能夠退出此循環(huán),同時(shí)執(zhí)行sigLock.notifyAll()喚醒調(diào)度器線(xiàn)程
sigLock.wait(1000L);
} catch (InterruptedException ignore) {}
}
......
}
......
// 如果線(xiàn)程池中的工作線(xiàn)程個(gè)數(shù) > 0
if(availThreadCount > 0) {
......
// 獲取馬上到時(shí)間的trigger
// 允許取出的trigger個(gè)數(shù)不能超過(guò)一個(gè)閥值,這個(gè)閥值是線(xiàn)程池個(gè)數(shù)與org.quartz.scheduler.batchTriggerAcquisitionMaxCount配置值間的最小者
triggers = qsRsrcs.getJobStore().acquireNextTriggers(
now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
......
// 執(zhí)行與trigger綁定的job
// shell是JobRunShell對(duì)象,實(shí)現(xiàn)了Runnable接口
// SimpleThreadPool.runInThread(Runnable)從線(xiàn)程池空閑列表中取出一個(gè)工作線(xiàn)程
// 工作線(xiàn)程執(zhí)行WorkerThread.run(Runnable),詳見(jiàn)下方WorkerThread的講解
if (qsRsrcs.getThreadPool().runInThread(shell) == false) { ...... }
} else {......}
......
} catch(RuntimeException re) {......}
} // while (!halted)
......
}
- WorkerThread.java
public void run(Runnable newRunnable) {
synchronized(lock) {
if(runnable != null) {
throw new IllegalStateException("Already running a Runnable!");
}
runnable = newRunnable;
lock.notifyAll();
}
}
// 工作線(xiàn)程一旦啟動(dòng),將一直運(yùn)行此方法
@Override
public void run() {
boolean ran = false;
// 工作線(xiàn)程一直循環(huán)等待job,直到線(xiàn)程被關(guān)閉,原理同QuartzSchedulerThread.run()中的halted.get()
while (run.get()) {
try {
// 原理同QuartzSchedulerThread.run()中的synchronized (sigLock)
// 鎖住lock,不斷循環(huán)等待job,當(dāng)job要被執(zhí)行時(shí),WorkerThread.run(Runnable)被調(diào)用,job運(yùn)行環(huán)境被賦值給runnable
synchronized(lock) {
while (runnable == null && run.get()) {
lock.wait(500);
}
// 開(kāi)始執(zhí)行job
if (runnable != null) {
ran = true;
// runnable.run()將觸發(fā)運(yùn)行job實(shí)現(xiàn)類(lèi)(比如JobImpl.execute())
runnable.run();
}
}
} catch (InterruptedException unblock) {
......
}
}
......
}
總的來(lái)說(shuō),核心代碼就是在while循環(huán)中調(diào)用Object.wait()
,等待可以跳出while循環(huán)的條件成立,當(dāng)條件成立時(shí),立馬調(diào)度Object.notifyAll()
使線(xiàn)程跳出while。通過(guò)這樣的代碼,可以實(shí)現(xiàn)調(diào)度器線(xiàn)程等待啟動(dòng)、工作線(xiàn)程等待job等功能。
如何避免GC
Quartz里提供了一種方案,用來(lái)避免某些對(duì)象被GC。方案其實(shí)簡(jiǎn)單而實(shí)用,就是QuartzScheduler類(lèi)
創(chuàng)建了一個(gè)列表ArrayList<Object>(5) holdToPreventGC
,如果某對(duì)象被add進(jìn)該列表,則意味著QuartzScheduler
實(shí)例引用了此對(duì)象,那么此對(duì)象至少在QuartzScheduler
實(shí)例存活時(shí)不會(huì)被GC。
哪些對(duì)象要避免GC?通過(guò)源碼可看到,調(diào)度器池和db管理器對(duì)象被放入了holdToPreventGC
,但實(shí)際上兩種對(duì)象是static的,而static對(duì)象屬于GC root,應(yīng)該是不會(huì)被GC的,所以即使不放入holdToPreventGC
,這兩種對(duì)象也不會(huì)被GC,除非被class unload或jvm生命結(jié)束。
static變量所指對(duì)象在heap中,如果變量不再指向該對(duì)象,比如賦值為null,對(duì)象會(huì)被GC