最近項目中好多地方都需要用到定時器,一開始用的是netty的hashWheel,后來發現刪除任務的時候不是很好刪除,於是就放棄了,然后選擇了Quartz。
- hashWheel定時器和Quartz的區別:
1)Quartz將定時任務分為任務和觸發器,而hashWheel只有任務的概念
2)Quartz通過一個TreeSet對所有的觸發器進行管理,而hashWheel通過一個hash輪來對所有的任務進行管理
3)Quartzl能夠非常方便的刪除定時任務,而netty的hashWheel暫時沒有刪除任務的接口(除非自己實現一個hashWheel定時器)
4)Quartz有一個專門的調度線程對任務進行管理,任務執行有另外專門的線程池,而hashWheel用一個線程實現對任務的管理和任務的執行。
5)Quartz能夠通過序列化,將定時任務保存在數據庫,而hashWheel不能
總的來說,Quartz的功能相對強大,而hashWheel相對要輕量級一點。
- Quartz定時器原理:
接下來就講講Quartz的原理。
1)首先任務調度器調度的時序大致如下所示:
在這里將幾個重要的類調用的過程以序列圖的形式展現出來,上半部分展現的是啟動過程,下半部分展現的是任務調度的過程。
步驟1.用戶首先需要生成一個調度器工廠SchedulerFactory,可以用下面的方式實現自己的定制化:
1 Properties properties=new Properties(); properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool"); 2 properties.put("org.quartz.threadPool.threadCount","10"); 3 SchedulerFactory sf=new StdSchedulerFactory(properties);
步驟2.然后通過getScheduler()方法從調度器工廠里得到調度器實例,首先查找有沒有這樣的調度器,沒有的話,就生成一個,有的話直接返回。所以得到的一般是單例,即默認的調度器。
步驟3.Scheduler有一個QuartzSchedulerThread(Thread的子類)屬性,在scheduler實例化的時候,實例化了一個對象,並用ThreadExecutor啟動該線程對象。該線程就是調度線程,主要任務就是不停的從JobStore中獲取即將被觸發的觸發器(默認30s調度一次)。在這個時候調度線程雖然啟動,但是處於pause狀態。
步驟4.接下來是任務調度的部分:
1 Scheduler scheduler=sf.getScheduler(); 2 scheduler.addJobListener(new TaskListener()); 3 scheduler.scheduleJob(jobDetail, simpleTrigger); 4 scheduler.start();
client通過scheduleJob()方法將任務和觸發器存儲在JobStore中,通過start()方法將QuartzSchedulerThread的pause狀態設為false,通知調度線程執行任務,此后調度線程不停的從JobStore中去取即將觸發的任務。
2)任務執行的時序如下所示:
上半部分展現的是任務執行之前准備工作的時序,下半部分展現的是任務執行的時序。
步驟1.調度線程首先去線程池中獲取可用的線程,如果沒有的話,就阻塞。
步驟2.從JobStore(從存儲介質中獲取觸發器,存儲介質可以是內存也可以是數據庫)獲取(接下來30s內的)觸發器,然后等待該觸發器觸發。
步驟3.調度線程創建一個JobRunShell(就是一個Runnable),然后從線程池中調用線程執行該任務。
接下來就是任務執行的時序:
步驟4.獲取trigger、JobDetail以及生成Job實例,然后執行job的execute接口函數。
3)持久化的任務的執行時序如下:

以上就是Quartz的基本工作流程。
我在使用的時候遇到的一些問題:
1.Quartz與Spring的整合-Quartz中的job如何自動注入spring容器托管的對象?
這個問題網上已經有解決方法,但是按照它的步驟執行之后還是不行,后來經過嘗試發現,在實現接口ApplicationContextAware的時候,需要將private ApplicationContext applicationContext;改成靜態的private static ApplicationContext applicationContext,
之后這個問題就得到完美解決了。
彩蛋:
