導(dǎo)語:作為java領(lǐng)域最受歡迎的任務(wù)調(diào)度庫之一,quartz為開發(fā)者提供了豐富的任務(wù)調(diào)度功能,比如讓某段程序在每天18:00準時執(zhí)行葛碧。本文將通過demo和源碼,講解quartz如何使用过吻、主要功能有哪些进泼、原理是什么,并挑選幾段有用的源碼片段進行解讀纤虽。
1乳绕、quartz簡介
quartz,即石英的意思廓推,隱喻如石英表般對時間的準確把握刷袍。
quartz是一個由java編寫的任務(wù)調(diào)度庫,由OpenSymphony組織開源出來樊展。那么問題來了呻纹,任務(wù)調(diào)度是個什么東西?舉個栗子专缠,現(xiàn)在有N個任務(wù)(程序)雷酪,要求在指定時間執(zhí)行,比如每周二3點執(zhí)行任務(wù)A涝婉、每天相隔5s執(zhí)行任務(wù)B等等哥力,這種多任務(wù)擁有多種執(zhí)行策略就是任務(wù)調(diào)度。而quartz的核心作用,是使任務(wù)調(diào)度變得豐富吩跋、高效寞射、安全,開發(fā)者只需要調(diào)幾個quartz接口并做簡單配置锌钮,即可實現(xiàn)上述需求桥温。
quartz號稱能夠同時對上萬個任務(wù)進行調(diào)度,擁有豐富的功能特性梁丘,包括任務(wù)調(diào)度侵浸、任務(wù)持久化、可集群化氛谜、插件等掏觉。目前最新版本是2.2.3,從github[1]上看值漫,2.2.4已在開發(fā)中澳腹。
quartz有競品嗎踩晶?有贯溅,那就是java Timer。quartz相對于java Timer的優(yōu)勢包括任務(wù)持久化翁涤、配置更豐富晚吞、線程池等等延旧,詳見官網(wǎng)[2]解釋,兩者在Spring上的用法可見這篇文章[^OpenSymphony Quartz和java Timer]槽地。
接下來迁沫,筆者將從一個簡單的demo開始,順著demo里使用到的quartz接口捌蚊,逐個分析quartz主要功能及其原理集畅。限于篇幅,demo中未涉及的功能缅糟,本文不涉及挺智,比如集群化等。最后窗宦,挑選2段對大家日常開發(fā)有用的源碼進行解讀赦颇。
讀這篇文章有什么用
- 對一個任務(wù)調(diào)度系統(tǒng)產(chǎn)生初步的原理級了解
- 更正確地使用quartz
- 學(xué)到如何采用多線程進行任務(wù)調(diào)度的源碼
- 學(xué)到如何避免GC的源碼
- 精(xian)力(de)充(dan)沛(teng),隨便找篇技術(shù)文讀讀
[^OpenSymphony Quartz和java Timer]: yaerfeng:Spring定時器配置的兩種實現(xiàn)方式OpenSymphony Quartz和java Timer詳解
2赴涵、使用代碼demo[3]
本文的demo程序由2個java文件和quartz.properties組成媒怯,quartz.properties是可選的,因為quartz有默認配置髓窜。demo實現(xiàn)從當前時間開始扇苞,每隔2s執(zhí)行一次JobImpl類的execute()方法欺殿。
- TestQuartz.java
/**
* 從當前時間開始,每隔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)度器開始調(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)度器名鳖敷,默認名是QuartzScheduler
org.quartz.scheduler.instanceName: TestQuartzScheduler
#============================================================================
# Configure ThreadPool 配置線程池
#============================================================================
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
#============================================================================
# Configure JobStore 配置任務(wù)存儲方式
#============================================================================
#相當于掃描頻率
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()
啟動調(diào)度至扰,每隔2s觸發(fā)執(zhí)行JobImpl.execute()
,打印出job impl running
资锰。
對于quartz.properties敢课,簡單場景下,開發(fā)者不用自定義配置绷杜,使用quartz默認配置即可直秆,但在要求較高的使用場景中還是要自定義配置,比如通過org.quartz.threadPool.threadCount
設(shè)置足夠的線程數(shù)可提高多job場景下的運行性能鞭盟。更詳盡的配置見官網(wǎng)配置說明頁圾结。
如果讓我們設(shè)計一個任務(wù)調(diào)度系統(tǒng),會像quartz那樣將job齿诉、trigger筝野、scheduler解藕嗎?quartz這樣設(shè)計的原因粤剧,筆者認為有兩點:
- job與trigger解藕歇竟,其實就是將任務(wù)本身和任務(wù)執(zhí)行策略解藕,這樣可以方便實現(xiàn)N個任務(wù)和M個執(zhí)行策略自由組合抵恋,比較容易理解焕议;
- scheduler單獨分離出來,相當于一個指揮官弧关,可以從全局做調(diào)度盅安,比如監(jiān)聽哪些trigger已經(jīng)ready、分配線程等等世囊,如果沒有scheduler宽堆,則trigger間會競爭混亂,難以實現(xiàn)諸如trigger優(yōu)先級等功能茸习,也無法合理使用資源畜隶。
下面,筆者將分別就job、trigger籽慢、scheduler進行原理分析浸遗。
3、job(任務(wù))
job由若干個class
和interface
實現(xiàn)箱亿。
Job接口
開發(fā)者想要job完成什么樣的功能跛锌,必須且只能由開發(fā)者自己動手來編寫實現(xiàn),比如demo中的JobImpl
届惋,這點無容置疑髓帽。但要想讓自己的job被quartz識別,就必須按照quartz的規(guī)則來辦事脑豹,這個規(guī)則就是job實現(xiàn)類必須實現(xiàn)Job接口郑藏,比如JobImpl
就實現(xiàn)了Job
。
Job
只有一個execute(JobExecutionContext)
瘩欺,JobExecutionContext
保存了job的上下文信息必盖,比如綁定的是哪個trigger。job實現(xiàn)類必須重寫execute()
俱饿,執(zhí)行job實際上就是運行execute()
歌粥。
JobDetailImpl類 / JobDetail接口
JobDetailImpl類
實現(xiàn)了JobDetail接口
,用來描述一個job拍埠,定義了job所有屬性及其get/set方法失驶。了解job擁有哪些屬性,就能知道quartz能提供什么樣的能力枣购,下面筆者用表格列出job若干核心屬性突勇。
屬性名 | 說明 |
---|---|
class | 必須是job實現(xiàn)類(比如JobImpl ),用來綁定一個具體job |
name | job名稱坷虑。如果未指定甲馋,會自動分配一個唯一名稱。所有job都必須擁有一個唯一name迄损,如果兩個job的name重復(fù)定躏,則只有最前面的job能被調(diào)度 |
group | job所屬的組名 |
description | job描述 |
durability | 是否持久化。如果job設(shè)置為非持久芹敌,當沒有活躍的trigger與之關(guān)聯(lián)的時候痊远,job會自動從scheduler中刪除。也就是說氏捞,非持久job的生命期是由trigger的存在與否決定的 |
shouldRecover | 是否可恢復(fù)碧聪。如果job設(shè)置為可恢復(fù),一旦job執(zhí)行時scheduler發(fā)生hard shutdown(比如進程崩潰或關(guān)機)液茎,當scheduler重啟后逞姿,該job會被重新執(zhí)行 |
jobDataMap | 除了上面常規(guī)屬性外辞嗡,用戶可以把任意kv數(shù)據(jù)存入jobDataMap,實現(xiàn)job屬性的無限制擴展滞造,執(zhí)行job時可以使用這些屬性數(shù)據(jù)续室。此屬性的類型是JobDataMap ,實現(xiàn)了Serializable接口 谒养,可做跨平臺的序列化傳輸 |
JobBuilder類
// 創(chuàng)建任務(wù)
JobDetail jobDetail = JobBuilder.newJob(JobImpl.class).withIdentity("myJob", "jobGroup").build();
上面代碼是demo一個片段挺狰,可以看出JobBuilder類
的作用:接收job實現(xiàn)類JobImpl
,生成JobDetail
實例买窟,默認生成JobDetailImpl
實例丰泊。
這里運用了建造者模式:JobImpl
相當于Product;JobDetail
相當于Builder始绍,擁有job的各種屬性及其get/set方法瞳购;JobBuilder
相當于Director,可為一個job組裝各種屬性疆虚。
4、trigger(觸發(fā)器)
trigger由若干個class
和interface
實現(xiàn)满葛。
SimpleTriggerImpl類 / SimpleTrigger接口 / Trigger接口
SimpleTriggerImpl類
實現(xiàn)了SimpleTrigger接口
径簿,SimpleTrigger接口
繼承了Trigger接口
,它們表示觸發(fā)器嘀韧,用來保存觸發(fā)job的策略篇亭,比如每隔幾秒觸發(fā)job。實際上锄贷,quartz有兩大觸發(fā)器:SimpleTrigger
和CronTrigger
译蒂,限于篇幅,本文僅介紹SimpleTrigger
谊却。
Trigger諸類保存了trigger所有屬性柔昼,同job屬性一樣,了解trigger屬性有助于我們了解quartz能提供什么樣的能力炎辨,下面筆者用表格列出trigger若干核心屬性捕透。
在quartz源碼或注釋中,經(jīng)常使用fire(點火)這個動詞來命名屬性名碴萧,表示觸發(fā)job乙嘀。
屬性名 | 屬性類型 | 說明 |
---|---|---|
name | 所有trigger通用 | trigger名稱 |
group | 所有trigger通用 | trigger所屬的組名 |
description | 所有trigger通用 | trigger描述 |
calendarName | 所有trigger通用 | 日歷名稱,指定使用哪個Calendar類破喻,經(jīng)常用來從trigger的調(diào)度計劃中排除某些時間段 |
misfireInstruction | 所有trigger通用 | 錯過job(未在指定時間執(zhí)行的job)的處理策略虎谢,默認為MISFIRE_INSTRUCTION_SMART_POLICY 。詳見這篇blog^Quartz misfire
|
priority | 所有trigger通用 | 優(yōu)先級曹质,默認為5 婴噩。當多個trigger同時觸發(fā)job時擎场,線程池可能不夠用,此時根據(jù)優(yōu)先級來決定誰先觸發(fā) |
jobDataMap | 所有trigger通用 | 同job的jobDataMap讳推。假如job和trigger的jobDataMap有同名key顶籽,通過getMergedJobDataMap() 獲取的jobDataMap,將以trigger的為準 |
startTime | 所有trigger通用 | 觸發(fā)開始時間银觅,默認為當前時間礼饱。決定什么時間開始觸發(fā)job |
endTime | 所有trigger通用 | 觸發(fā)結(jié)束時間。決定什么時間停止觸發(fā)job |
nextFireTime | SimpleTrigger私有 | 下一次觸發(fā)job的時間 |
previousFireTime | SimpleTrigger私有 | 上一次觸發(fā)job的時間 |
repeatCount | SimpleTrigger私有 | 需觸發(fā)的總次數(shù) |
timesTriggered | SimpleTrigger私有 | 已經(jīng)觸發(fā)過的次數(shù) |
repeatInterval | SimpleTrigger私有 | 觸發(fā)間隔時間 |
TriggerBuilder類
// 創(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一個片段究驴,可以看出TriggerBuilder類
的作用:生成Trigger實例镊绪,默認生成SimpleTriggerImpl
實例。同JobBuilder
一樣洒忧,這里也運用了建造者模式蝴韭。
5、scheduler(調(diào)度器)
scheduler主要由StdScheduler類
熙侍、Scheduler接口
榄鉴、StdSchedulerFactory類
、SchedulerFactory接口
蛉抓、QuartzScheduler類
實現(xiàn)庆尘,它們的關(guān)系見下面UML圖。
// 創(chuàng)建調(diào)度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
......
// 將任務(wù)及其觸發(fā)器放入調(diào)度器
scheduler.scheduleJob(jobDetail, trigger);
// 調(diào)度器開始調(diào)度任務(wù)
scheduler.start();
上面代碼是demo一個片段巷送,可以看出這里運用了工廠模式驶忌,通過factory類(StdSchedulerFactory
)生產(chǎn)出scheduler實例(StdScheduler
)。scheduler是整個quartz的關(guān)鍵笑跛,為此付魔,筆者把demo中用到的scheduler接口的源碼加上中文注釋做個講解。
- StdSchedulerFactory.getScheduler()源碼
public Scheduler getScheduler() throws SchedulerException {
// 讀取quartz配置文件飞蹂,未指定則順序遍歷各個path下的quartz.properties文件
// 解析出quartz配置內(nèi)容和環(huán)境變量几苍,存入PropertiesParser對象
// PropertiesParser組合了Properties(繼承Hashtable),定義了一系列對Properties的操作方法陈哑,比如getPropertyGroup()批量獲取相同前綴的配置擦剑。配置內(nèi)容和環(huán)境變量存放在Properties成員變量中
if (cfg == null) {
initialize();
}
// 獲取調(diào)度器池,采用了單例模式
// 其實芥颈,調(diào)度器池的核心變量就是一個hashmap惠勒,每個元素key是scheduler名,value是scheduler實例
// getInstance()用synchronized防止并發(fā)創(chuàng)建
SchedulerRepository schedRep = SchedulerRepository.getInstance();
// 從調(diào)度器池中取出當前配置所用的調(diào)度器
Scheduler sched = schedRep.lookup(getSchedulerName());
......
// 如果調(diào)度器池中沒有當前配置的調(diào)度器爬坑,則實例化一個調(diào)度器纠屋,主要動作包括:
// 1)初始化threadPool(線程池):開發(fā)者可以通過org.quartz.threadPool.class配置指定使用哪個線程池類,比如SimpleThreadPool盾计。先class load線程池類售担,接著動態(tài)生成線程池實例bean赁遗,然后通過反射,使用setXXX()方法將以org.quartz.threadPool開頭的配置內(nèi)容賦值給bean成員變量族铆;
// 2)初始化jobStore(任務(wù)存儲方式):開發(fā)者可以通過org.quartz.jobStore.class配置指定使用哪個任務(wù)存儲類岩四,比如RAMJobStore。先class load任務(wù)存儲類哥攘,接著動態(tài)生成實例bean剖煌,然后通過反射,使用setXXX()方法將以org.quartz.jobStore開頭的配置內(nèi)容賦值給bean成員變量逝淹;
// 3)初始化dataSource(數(shù)據(jù)源):開發(fā)者可以通過org.quartz.dataSource配置指定數(shù)據(jù)源詳情耕姊,比如哪個數(shù)據(jù)庫、賬號栅葡、密碼等茉兰。jobStore要指定為JDBCJobStore,dataSource才會有效欣簇;
// 4)初始化其他配置:包括SchedulerPlugins规脸、JobListeners、TriggerListeners等熊咽;
// 5)初始化threadExecutor(線程執(zhí)行器):默認為DefaultThreadExecutor莫鸭;
// 6)創(chuàng)建工作線程:根據(jù)配置創(chuàng)建N個工作thread,執(zhí)行start()啟動thread网棍,并將N個thread順序add進threadPool實例的空閑線程列表availWorkers中黔龟;
// 7)創(chuàng)建調(diào)度器線程:創(chuàng)建QuartzSchedulerThread實例妇智,并通過threadExecutor.execute(實例)啟動調(diào)度器線程滥玷;
// 8)創(chuàng)建調(diào)度器:創(chuàng)建StdScheduler實例,將上面所有配置和引用組合進實例中巍棱,并將實例存入調(diào)度器池中
sched = instantiate();
return sched;
}
上面有個過程是初始化jobStore惑畴,表示使用哪種方式存儲scheduler相關(guān)數(shù)據(jù)。quartz有兩大jobStore:RAMJobStore
和JDBCJobStore
航徙。RAMJobStore
把數(shù)據(jù)存入內(nèi)存如贷,性能最高,配置也簡單到踏,但缺點是系統(tǒng)掛了難以恢復(fù)數(shù)據(jù)杠袱。JDBCJobStore
保存數(shù)據(jù)到數(shù)據(jù)庫,保證數(shù)據(jù)的可恢復(fù)性窝稿,但性能較差且配置復(fù)雜楣富。
- QuartzScheduler.scheduleJob(JobDetail, Trigger)源碼
public Date scheduleJob(JobDetail jobDetail,
Trigger trigger) throws SchedulerException {
// 檢查調(diào)度器是否開啟,如果關(guān)閉則throw異常到上層
validateState();
......
// 獲取trigger首次觸發(fā)job的時間伴榔,以此時間為起點纹蝴,每隔一段指定的時間觸發(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注冊進調(diào)度器的jobStore
resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
// 通知job監(jiān)聽者
notifySchedulerListenersJobAdded(jobDetail);
// 通知調(diào)度器線程
notifySchedulerThread(trigger.getNextFireTime().getTime());
// 通知trigger監(jiān)聽者
notifySchedulerListenersSchduled(trigger);
return ft;
}
- QuartzScheduler.start()源碼
public void start() throws SchedulerException {
......
// 這句最關(guān)鍵庄萎,作用是使調(diào)度器線程跳出一個無限循環(huán),開始輪詢所有trigger觸發(fā)job
// 原理詳見“如何采用多線程進行任務(wù)調(diào)度”
schedThread.togglePause(false);
......
}
6塘安、quartz線程模型
上圖是筆者在eclipse中調(diào)試demo時的線程圖糠涛,可以見到,除了第一條主線程外兼犯,還有10條工作線程忍捡,和1條調(diào)度器線程。
工作線程以{instanceName}_Worker-{[1-10]}
命名免都。線程數(shù)目由quart.properties文件中的org.quartz.threadPool.threadCount
配置項指定锉罐。所有工作線程都會放在線程池中,即所有工作線程都放在SimpleThreadPool
實例的一個LinkedList<WorkerThread>成員變量中绕娘。WorkerThread
是SimpleThreadPool
的內(nèi)部類脓规,這么設(shè)計可能是因為不想繼承SimpleThreadPool
但又想調(diào)用其protected方法,或者想隱藏WorkerThread
险领。線程池還擁有兩個LinkedList<WorkerThread>:availWorkers
和busyWorkers
侨舆,分別存放空閑和正在執(zhí)行job的工作線程。
調(diào)度器線程以{instanceName}_QuartzSchedulerThread
命名绢陌。該線程將根據(jù)trigger找出要待運行job挨下,然后從threadpool中拿出工作線程來執(zhí)行。調(diào)度器線程主體是QuartzSchedulerThread
對象脐湾。
{instanceName}
指的是quart.properties文件中的org.quartz.scheduler.instanceName
配置值臭笆,這里是TestQuartzScheduler
。[1-10]
表示從1到10的任意數(shù)字秤掌。
7愁铺、精彩源碼解讀
本節(jié)中,筆者從quartz源碼中挑選了兩段代碼闻鉴,之所以選擇這兩段代碼茵乱,是因為它們實現(xiàn)了線程間通信、加鎖同步孟岛、避免GC等功能瓶竭,對工程師們很有幫助。
如何采用多線程進行任務(wù)調(diào)度
- QuartzSchedulerThread.java
// 調(diào)度器線程一旦啟動渠羞,將一直運行此方法
public void run() {
......
// while()無限循環(huán)斤贰,每次循環(huán)取出時間將到的trigger,觸發(fā)對應(yīng)的job次询,直到調(diào)度器線程被關(guān)閉
// halted是一個AtomicBoolean類變量荧恍,有個volatile int變量value,其get()方法僅僅簡單的一句return value != 0渗蟹,get()返回結(jié)果表示調(diào)度器線程是否開關(guān)
// volatile修飾的變量块饺,存取必須走內(nèi)存赞辩,不能通過cpu緩存,這樣一來get總能獲得set的最新真實值授艰,因此volatile變量適合用來存放簡單的狀態(tài)信息
// 顧名思義辨嗽,AtomicBoolean要解決原子性問題,但volatile并不能保證原子性淮腾,詳見http://blog.csdn.net/wxwzy738/article/details/43238089
while (!halted.get()) {
try {
// check if we're supposed to pause...
// sigLock是個Object對象糟需,被用于加鎖同步
// 需要用到wait(),必須加到synchronized塊內(nèi)
synchronized (sigLock) {
while (paused && !halted.get()) {
try {
// wait until togglePause(false) is called...
// 這里會不斷循環(huán)等待谷朝,直到QuartzScheduler.start()調(diào)用了togglePause(false)
// 調(diào)用wait()洲押,調(diào)度器線程進入休眠狀態(tài),同時sigLock鎖被釋放
// togglePause(false)獲得sigLock鎖圆凰,將paused置為false杈帐,使調(diào)度器線程能夠退出此循環(huán),同時執(zhí)行sigLock.notifyAll()喚醒調(diào)度器線程
sigLock.wait(1000L);
} catch (InterruptedException ignore) {}
}
......
}
......
// 如果線程池中的工作線程個數(shù) > 0
if(availThreadCount > 0) {
......
// 獲取馬上到時間的trigger
// 允許取出的trigger個數(shù)不能超過一個閥值专钉,這個閥值是線程池個數(shù)與org.quartz.scheduler.batchTriggerAcquisitionMaxCount配置值間的最小者
triggers = qsRsrcs.getJobStore().acquireNextTriggers(
now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
......
// 執(zhí)行與trigger綁定的job
// shell是JobRunShell對象挑童,實現(xiàn)了Runnable接口
// SimpleThreadPool.runInThread(Runnable)從線程池空閑列表中取出一個工作線程
// 工作線程執(zhí)行WorkerThread.run(Runnable),詳見下方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();
}
}
// 工作線程一旦啟動跃须,將一直運行此方法
@Override
public void run() {
boolean ran = false;
// 工作線程一直循環(huán)等待job站叼,直到線程被關(guān)閉,原理同QuartzSchedulerThread.run()中的halted.get()
while (run.get()) {
try {
// 原理同QuartzSchedulerThread.run()中的synchronized (sigLock)
// 鎖住lock菇民,不斷循環(huán)等待job尽楔,當job要被執(zhí)行時,WorkerThread.run(Runnable)被調(diào)用第练,job運行環(huán)境被賦值給runnable
synchronized(lock) {
while (runnable == null && run.get()) {
lock.wait(500);
}
// 開始執(zhí)行job
if (runnable != null) {
ran = true;
// runnable.run()將觸發(fā)運行job實現(xiàn)類(比如JobImpl.execute())
runnable.run();
}
}
} catch (InterruptedException unblock) {
......
}
}
......
}
總的來說阔馋,核心代碼就是在while循環(huán)中調(diào)用Object.wait()
,等待可以跳出while循環(huán)的條件成立复旬,當條件成立時垦缅,立馬調(diào)度Object.notifyAll()
使線程跳出while冲泥。通過這樣的代碼驹碍,可以實現(xiàn)調(diào)度器線程等待啟動、工作線程等待job等功能凡恍。
如何避免GC
Quartz里提供了一種方案志秃,用來避免某些對象被GC。方案其實簡單而實用嚼酝,就是QuartzScheduler類
創(chuàng)建了一個列表ArrayList<Object>(5) holdToPreventGC
浮还,如果某對象被add進該列表,則意味著QuartzScheduler
實例引用了此對象闽巩,那么此對象至少在QuartzScheduler
實例存活時不會被GC钧舌。
哪些對象要避免GC担汤?通過源碼可看到,調(diào)度器池和db管理器對象被放入了holdToPreventGC
洼冻,但實際上兩種對象是static的崭歧,而static對象屬于GC root,應(yīng)該是不會被GC的撞牢,所以即使不放入holdToPreventGC
率碾,這兩種對象也不會被GC,除非被class unload或jvm生命結(jié)束屋彪。
static變量所指對象在heap中所宰,如果變量不再指向該對象,比如賦值為null畜挥,對象會被GC