第一步:引包(Maven)
<!-- 定時任務 --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.2</version> </dependency>
第二步:創建要被定執行的任務類
這一步也很簡單,只需要創建一個實現了org.quartz.Job接口的類,並實現這個接口的唯一一個方法execute(JobExecutionContext arg0) throws JobExecutionException即可。如:
package com.fync.quartz; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * 創建要被定執行的任務類 * @author long * */ public class MyJob implements Job{ public void execute(JobExecutionContext context) throws JobExecutionException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); System.out.println(sdf.format(new Date())); } }
第三步:創建任務調度,並執行
package com.fync.quartz; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; /** * 創建任務調度,並執行 * @author long * */ public class MainScheduler { //創建調度器 public static Scheduler getScheduler() throws SchedulerException{ SchedulerFactory schedulerFactory = new StdSchedulerFactory(); return schedulerFactory.getScheduler(); } public static void schedulerJob() throws SchedulerException{ //創建任務 JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build(); //創建觸發器 每3秒鍾執行一次 Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group3") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()) .build(); Scheduler scheduler = getScheduler(); //將任務及其觸發器放入調度器 scheduler.scheduleJob(jobDetail, trigger); //調度器開始調度任務 scheduler.start(); } public static void main(String[] args) throws SchedulerException { MainScheduler mainScheduler = new MainScheduler(); mainScheduler.schedulerJob(); } }
MainScheduler.main()
依次創建了scheduler(調度器)、job(任務)、trigger(觸發器),其中,job指定了MyJob,trigger保存job的觸發執行策略(每隔3s執行一次),scheduler將job和trigger綁定在一起,最后scheduler.start()
啟動調度,每隔3s觸發執行JobImpl.execute()
,
打印出當前時間。
除了SimpleScheduler之外,常用的還有CronTrigger.
原理分析:
1、job(任務)
job由若干個class
和interface
實現。
Job接口
開發者想要job完成什么樣的功能,必須且只能由開發者自己動手來編寫實現,比如demo中的MyJob
,這點無容置疑。但要想讓自己的job被quartz識別,就必須按照quartz的規則來辦事,這個規則就是job實現類必須實現Job接口,比如MyJob就實現了Job
。
Job
只有一個execute(JobExecutionContext)
,JobExecutionContext
保存了job的上下文信息,比如綁定的是哪個trigger。job實現類必須重寫execute()
,執行job實際上就是運行execute()
。
2、JobDetailImpl類 / JobDetail接口
JobDetailImpl類
實現了JobDetail接口
,用來描述一個job,定義了job所有屬性及其get/set方法。了解job擁有哪些屬性,就能知道quartz能提供什么樣的能力,下面用表格列出job若干核心屬性。
屬性名 | 說明 |
---|---|
class | 必須是job實現類(比如JobImpl ),用來綁定一個具體job |
name | job名稱。如果未指定,會自動分配一個唯一名稱。所有job都必須擁有一個唯一name,如果兩個job的name重復,則只有最前面的job能被調度 |
group | job所屬的組名 |
description | job描述 |
durability | 是否持久化。如果job設置為非持久,當沒有活躍的trigger與之關聯的時候,job會自動從scheduler中刪除。也就是說,非持久job的生命期是由trigger的存在與否決定的 |
shouldRecover | 是否可恢復。如果job設置為可恢復,一旦job執行時scheduler發生hard shutdown(比如進程崩潰或關機),當scheduler重啟后,該job會被重新執行 |
jobDataMap | 除了上面常規屬性外,用戶可以把任意kv數據存入jobDataMap,實現job屬性的無限制擴展,執行job時可以使用這些屬性數據。此屬性的類型是JobDataMap ,實現了Serializable接口 ,可做跨平台的序列化傳輸 |
3、JobBuilder類
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
上面代碼是demo一個片段,可以看出JobBuilder類
的作用:接收job實現類MyJob
,生成JobDetail
實例,默認生成JobDetailImpl
實例。
這里運用了建造者模式:JobImpl
相當於Product;JobDetail
相當於Builder,擁有job的各種屬性及其get/set方法;JobBuilder
相當於Director,可為一個job組裝各種屬性。
4、trigger(觸發器)
trigger由若干個class
和interface
實現。
SimpleTriggerImpl類 / SimpleTrigger接口 / Trigger接口
SimpleTriggerImpl類
實現了SimpleTrigger接口
,SimpleTrigger接口
繼承了Trigger接口
,它們表示觸發器,用來保存觸發job的策略,比如每隔幾秒觸發job。實際上,quartz有兩大觸發器:SimpleTrigger
和CronTrigger
,限於篇幅,本文僅介紹SimpleTrigger
。
Trigger諸類保存了trigger所有屬性,同job屬性一樣,了解trigger屬性有助於我們了解quartz能提供什么樣的能力,下面用表格列出trigger若干核心屬性。
在quartz源碼或注釋中,經常使用fire(點火)這個動詞來命名屬性名,表示觸發job。
屬性名 | 屬性類型 | 說明 |
---|---|---|
name | 所有trigger通用 | trigger名稱 |
group | 所有trigger通用 | trigger所屬的組名 |
description | 所有trigger通用 | trigger描述 |
calendarName | 所有trigger通用 | 日歷名稱,指定使用哪個Calendar類,經常用來從trigger的調度計划中排除某些時間段 |
misfireInstruction | 所有trigger通用 | 錯過job(未在指定時間執行的job)的處理策略,默認為MISFIRE_INSTRUCTION_SMART_POLICY 。詳見這篇blog[5] |
priority | 所有trigger通用 | 優先級,默認為5 。當多個trigger同時觸發job時,線程池可能不夠用,此時根據優先級來決定誰先觸發 |
jobDataMap | 所有trigger通用 | 同job的jobDataMap。假如job和trigger的jobDataMap有同名key,通過getMergedJobDataMap() 獲取的jobDataMap,將以trigger的為准 |
startTime | 所有trigger通用 | 觸發開始時間,默認為當前時間。決定什么時間開始觸發job |
endTime | 所有trigger通用 | 觸發結束時間。決定什么時間停止觸發job |
nextFireTime | SimpleTrigger私有 | 下一次觸發job的時間 |
previousFireTime | SimpleTrigger私有 | 上一次觸發job的時間 |
repeatCount | SimpleTrigger私有 | 需觸發的總次數 |
timesTriggered | SimpleTrigger私有 | 已經觸發過的次數 |
repeatInterval | SimpleTrigger私有 | 觸發間隔時間 |
5、TriggerBuilder類
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group3") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()) .build();
上面代碼是demo一個片段,可以看出TriggerBuilder類
的作用:生成Trigger實例,默認生成SimpleTriggerImpl
實例。同JobBuilder
一樣,這里也運用了建造者模式。
6、scheduler(調度器)
scheduler主要由StdScheduler類
、Scheduler接口
、StdSchedulerFactory類
、SchedulerFactory接口
、QuartzScheduler類
實現,它們的關系見下面UML圖。

// 創建調度器 SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); ...... // 將任務及其觸發器放入調度器 scheduler.scheduleJob(jobDetail, trigger); // 調度器開始調度任務 scheduler.start();
上面代碼是demo一個片段,可以看出這里運用了工廠模式,通過factory類(StdSchedulerFactory
)生產出scheduler實例(StdScheduler
)。scheduler是整個quartz的關鍵,為此,筆者把demo中用到的scheduler接口的源碼加上中文注釋做個講解。
- StdSchedulerFactory.getScheduler()源碼
public Scheduler getScheduler() throws SchedulerException { // 讀取quartz配置文件,未指定則順序遍歷各個path下的quartz.properties文件 // 解析出quartz配置內容和環境變量,存入PropertiesParser對象 // PropertiesParser組合了Properties(繼承Hashtable),定義了一系列對Properties的操作方法,比如getPropertyGroup()批量獲取相同前綴的配置。配置內容和環境變量存放在Properties成員變量中 if (cfg == null) { initialize(); } // 獲取調度器池,采用了單例模式 // 其實,調度器池的核心變量就是一個hashmap,每個元素key是scheduler名,value是scheduler實例 // getInstance()用synchronized防止並發創建 SchedulerRepository schedRep = SchedulerRepository.getInstance(); // 從調度器池中取出當前配置所用的調度器 Scheduler sched = schedRep.lookup(getSchedulerName()); ...... // 如果調度器池中沒有當前配置的調度器,則實例化一個調度器,主要動作包括: // 1)初始化threadPool(線程池):開發者可以通過org.quartz.threadPool.class配置指定使用哪個線程池類,比如SimpleThreadPool。先class load線程池類,接着動態生成線程池實例bean,然后通過反射,
// 使用setXXX()方法將以org.quartz.threadPool開頭的配置內容賦值給bean成員變量; // 2)初始化jobStore(任務存儲方式):開發者可以通過org.quartz.jobStore.class配置指定使用哪個任務存儲類,比如RAMJobStore。先class load任務存儲類,接着動態生成實例bean,然后通過反射,
// 使用setXXX()方法將以org.quartz.jobStore開頭的配置內容賦值給bean成員變量; // 3)初始化dataSource(數據源):開發者可以通過org.quartz.dataSource配置指定數據源詳情,比如哪個數據庫、賬號、密碼等。jobStore要指定為JDBCJobStore,dataSource才會有效; // 4)初始化其他配置:包括SchedulerPlugins、JobListeners、TriggerListeners等; // 5)初始化threadExecutor(線程執行器):默認為DefaultThreadExecutor; // 6)創建工作線程:根據配置創建N個工作thread,執行start()啟動thread,並將N個thread順序add進threadPool實例的空閑線程列表availWorkers中; // 7)創建調度器線程:創建QuartzSchedulerThread實例,並通過threadExecutor.execute(實例)啟動調度器線程; // 8)創建調度器:創建StdScheduler實例,將上面所有配置和引用組合進實例中,並將實例存入調度器池中 sched = instantiate(); return sched; }
上面有個過程是初始化jobStore,表示使用哪種方式存儲scheduler相關數據。quartz有兩大jobStore:RAMJobStore
和JDBCJobStore
。RAMJobStore
把數據存入內存,性能最高,配置也簡單,但缺點是系統掛了難以恢復數據。JDBCJobStore
保存數據到數據庫,保證數據的可恢復性,但性能較差且配置復雜。

- QuartzScheduler.scheduleJob(JobDetail, Trigger)源碼
public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException { // 檢查調度器是否開啟,如果關閉則throw異常到上層 validateState(); ...... // 獲取trigger首次觸發job的時間,以此時間為起點,每隔一段指定的時間觸發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注冊進調度器的jobStore resources.getJobStore().storeJobAndTrigger(jobDetail, trig); // 通知job監聽者 notifySchedulerListenersJobAdded(jobDetail); // 通知調度器線程 notifySchedulerThread(trigger.getNextFireTime().getTime()); // 通知trigger監聽者 notifySchedulerListenersSchduled(trigger); return ft; }
- QuartzScheduler.start()源碼
public void start() throws SchedulerException { ...... // 這句最關鍵,作用是使調度器線程跳出一個無限循環,開始輪詢所有trigger觸發job // 原理詳見“如何采用多線程進行任務調度” schedThread.togglePause(false); ...... }