「Java系列」quartz原理揭秘和源碼解讀

導(dǎo)語:作為java領(lǐng)域最受歡迎的任務(wù)調(diào)度庫之一,quartz為開發(fā)者提供了豐富的任務(wù)調(diào)度功能,比如讓某段程序在每天18:00準時執(zhí)行葛碧。本文將通過demo和源碼,講解quartz如何使用过吻、主要功能有哪些进泼、原理是什么,并挑選幾段有用的源碼片段進行解讀纤虽。

quartz logo

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]: yaerfengSpring定時器配置的兩種實現(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)先級等功能茸习,也無法合理使用資源畜隶。

下面,筆者將分別就jobtrigger籽慢、scheduler進行原理分析浸遗。

3、job(任務(wù))

job由若干個classinterface實現(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由若干個classinterface實現(xiàn)满葛。

SimpleTriggerImpl類 / SimpleTrigger接口 / Trigger接口

SimpleTriggerImpl類實現(xiàn)了SimpleTrigger接口径簿,SimpleTrigger接口繼承了Trigger接口,它們表示觸發(fā)器嘀韧,用來保存觸發(fā)job的策略篇亭,比如每隔幾秒觸發(fā)job。實際上锄贷,quartz有兩大觸發(fā)器:SimpleTriggerCronTrigger译蒂,限于篇幅,本文僅介紹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圖。

scheduler 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:RAMJobStoreJDBCJobStore航徙。RAMJobStore把數(shù)據(jù)存入內(nèi)存如贷,性能最高,配置也簡單到踏,但缺點是系統(tǒng)掛了難以恢復(fù)數(shù)據(jù)杠袱。JDBCJobStore保存數(shù)據(jù)到數(shù)據(jù)庫,保證數(shù)據(jù)的可恢復(fù)性窝稿,但性能較差且配置復(fù)雜楣富。

兩種常見的任務(wù)存儲方式

  • 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線程模型

quartz運行時線程圖

上圖是筆者在eclipse中調(diào)試demo時的線程圖糠涛,可以見到,除了第一條主線程外兼犯,還有10條工作線程忍捡,和1條調(diào)度器線程。

工作線程以{instanceName}_Worker-{[1-10]}命名免都。線程數(shù)目由quart.properties文件中的org.quartz.threadPool.threadCount配置項指定锉罐。所有工作線程都會放在線程池中,即所有工作線程都放在SimpleThreadPool實例的一個LinkedList<WorkerThread>成員變量中绕娘。WorkerThreadSimpleThreadPool的內(nèi)部類脓规,這么設(shè)計可能是因為不想繼承SimpleThreadPool但又想調(diào)用其protected方法,或者想隱藏WorkerThread险领。線程池還擁有兩個LinkedList<WorkerThread>:availWorkersbusyWorkers侨舆,分別存放空閑和正在執(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


  1. https://github.com/quartz-scheduler/quartz ?

  2. http://www.quartz-scheduler.org ?

  3. https://github.com/star2478/java-quartz ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仔粥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蟹但,更是在濱河造成了極大的恐慌件炉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矮湘,死亡現(xiàn)場離奇詭異斟冕,居然都是意外死亡,警方通過查閱死者的電腦和手機缅阳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門磕蛇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人十办,你說我怎么就攤上這事秀撇。” “怎么了向族?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵呵燕,是天一觀的道長。 經(jīng)常有香客問我件相,道長再扭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任夜矗,我火速辦了婚禮泛范,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘紊撕。我一直安慰自己罢荡,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著区赵,像睡著了一般惭缰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笼才,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天从媚,我揣著相機與錄音,去河邊找鬼患整。 笑死拜效,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的各谚。 我是一名探鬼主播紧憾,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昌渤!你這毒婦竟也來了赴穗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤膀息,失蹤者是張志新(化名)和其女友劉穎般眉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潜支,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡甸赃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了冗酿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埠对。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖裁替,靈堂內(nèi)的尸體忽然破棺而出项玛,到底是詐尸還是另有隱情,我是刑警寧澤弱判,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布襟沮,位于F島的核電站,受9級特大地震影響昌腰,放射性物質(zhì)發(fā)生泄漏开伏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一剥哑、第九天 我趴在偏房一處隱蔽的房頂上張望硅则。 院中可真熱鬧淹父,春花似錦株婴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽大审。三九已至,卻和暖如春座哩,著一層夾襖步出監(jiān)牢的瞬間徒扶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工根穷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姜骡,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓屿良,卻偏偏與公主長得像圈澈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尘惧,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容