今天在開始關於線程的互斥之前,先對另外一個定時器spring提供的qurtar的用法做一個簡單的介紹,同時對比一下與java原生態提供的Timer的區別。
先上一個定時任務的配置吧,這是我們自己的項目中定時任務刪除每個表記錄的:

對於具體的任務的業務實現我們不說了,我就說一下quartor是如何調度的。在開始解讀源碼之前,我們先借用其他網友的觀點對這個進行一個概要的介紹:
1. Java定時器沒有持久化機制。這個在上一篇中看的TIMER的源碼確實沒有
2. Java定時器的日程管理不夠靈活(只能設置開始時間、重復的間隔,設置特定的日期、時間等)//這點感同身受
3. Java定時器沒有使用線程池(每個Java定時器使用一個線程)//想必在用timer是遇到了吧。
4. Java定時器沒有切實的管理方案,你不得不自己完成存儲、組織、恢復任務的措施
反正就是什么呢,Timer比較單一,如果是一些簡單的任務可以使用,企業級的一半還是使用quartor。
好了 下面咱們開始quartot源碼的讀吧。
讀源碼之前需要增加一個知識點:
1.InitializingBean 與 afterPorpertiesSet()
關於在spring 容器初始化 bean 和銷毀前所做的操作定義方式有三種:
第一種:通過@PostConstruct 和 @PreDestroy 方法 實現初始化和銷毀bean之前進行的操作
第二種是:通過 在xml中定義init-method 和 destory-method方法
第三種是: 通過bean實現InitializingBean和 DisposableBean接口
Spirng的InitializingBean為bean提供了定義初始化方法的方式。InitializingBean是一個接口,它僅僅包含一個方法:afterPropertiesSet()。 在spring 初始化后,執行完所有屬性設置方法(即setXxx)將自動調用 afterPropertiesSet(), 在配置文件中無須特別的配置, 但此方式增加了bean對spring 的依賴,應該盡量避免使用。
實現org.springframework.beans.factory.DisposableBean接口允許一個bean,可以在包含它的BeanFactory銷毀的時候得到一個回調。DisposableBean也只指定了一個方法:
void destroy() throws Exception;
在我使用的這里使用的是第三種,這也就是為什么在我們的定時任務配置文件中看不到init-method的配置的源碼。
好了這時候我們看源碼,先看SchedulerFactoryBean來創建scheduler實例的類的定義

在這個定義中我們看到SchedulerFactoryBean是顯現了initializingBean這個接口和DisposableBean接口,我們從上面的知識點中可以知道,在實現了initilizingBean這個接口的類中,會實現一個方法afterPropertiesSet()。執行這個方法是時機是spring在set所有屬性完成后,
下面是借鑒的一個benzero的網友的內容:在我寫這個博客的時候網上搜了一下,感覺這哥們寫的真是好,我寫的話並一定比人家的好,哈哈哈 好東西用來共享啦。但是要標明引自哪里,畢竟有版權啊。
具體來分析afterPropertiesSet()和start(),可以看出afterPropertiesSet()的核心步驟:
1. 實例化一個SchdulerFactory, 此處使用的是默認的StdSchedulerFactory.class
2. 通過Factory獲取一個scheduler實例容器。
3. 配置/添加 對該容器指定的listener、jobs、triggers等,本質是將注入的上述屬性配合到上步創建的Scheduler容器當中,比較簡單,后續忽略介紹。
代碼說明的截圖:

start()擇更為簡單,直接啟動上面創建的scheduler容器的實例,代碼說明截圖如下:其中有一個startupDelay的參數,即啟動延時,默認都是為0的,一般也是需要即時啟動的。

綜上,可以看到quatz啟動的大概步驟, 利用spring提供的兩個回調接口, 分別在bean實例的構造和容器啟動階段實現了 Scheduler容器的構造 和 scheduler的啟動。
當然理解架構只到此程度是遠遠不夠的,上述部分的核心是通過Factory獲取到一個scheduler實例,即createScheduler()方法,下一節對這個過程做出詳細的說明。
第二節.scheduler容器獲取(構造)過程
首先,createScheduler()之中的主流程調用樹結構如下,其核心是在instantiate()完成的,外部的2層主要做了些讀取配置和條件判斷等。

直接來看instantiate()的代碼,類比較大且復雜,只列出核心行

1.創建了一個線程池,默認使用的是SimpleThreadPool, 代碼中tp對象
2.創建了一個jobStore,默認使用的是RAMJobStore, 代碼中的js對象
3.創建一個QuartzScheduler對象, 代碼中的qs對象, 下圖列出他的創建樹。
大體上有4步,有2個關鍵點需要掌握
a.QuartzScheduler對象保存有QuartzSchedulerThread的引用,實際的調度工作基本上是由QuartzSchedulerThread這個對象做的. 當然實例化QuartzScheduler的目的主要也在於去實例化QuartzSchedulerThread。
b.QuartzSchedulerThread在構造函數中實例化,並一經創建就自啟動,它是一個獨立的線程,至於自啟后做什么,在后面章節說明。下面是quartzScheduler的創建過程

4.創建了scheduler.
4.1. QuartzSchedulerResources 對象,代碼中的rsrcs對象
Contains all of the resources,可以將其視為資源緩存對象,包含了所有定時模塊的資源的引用。
4.2. Scheduler對象。 看源代碼它的直接實例是StdScheduler,但實際StdScheduler中直接引用了QuartzScheduler(qs)且僅止於此,沒有額外的動作。 簡單的你可以認為這是一層封裝。因此你可以認為真正在工作的其實就是QuartzScheduler或者說是 QuartzSchedulerThread
本節主要講述了整個scheduler容器創建時的過程,以及4個重要的步驟節點。后續章節來分別詳述這些核心步驟的具體創建和工作原理。
至此,通過SchedulerFactory構建Scheduler的過程已完成,可以總結為三個要點。
1. 創建線程池
2. 創建JobStore
3. 通過QuartzScheduler啟動一個QuartzSchedulerThread
他們的關系如下圖,最終返回的這是QuatzScheduler的引用。本圖轉自網絡。

第三節. 對第二節的一個小結
通過對上節真個啟動過程的源碼解讀,應該可以較為深入的理解spring 管理的Quatz的啟動流程,網絡上一張時序圖可以作為很好的參考:

第四節.QuartzSchedulerThread是如何工作的。
在第二節中講到啟動是一個核心步驟就是創建QuartzSchedulerThread並啟動它,因為它是繼承自Thread的,因此我們可以跟蹤它的run方法看它究竟做哪些工作

解析這個方法如下:
1. 方法體是一個無限循環,一直在等任務的出現,然后啟動執行。
2. 剛被啟動時,是在等pause信號量。 這個信號在何時改變了,也行你已經在上面時序圖中看到了, 前面章節提到的start()的實現其本質的工作就是修改這個pause信號量。 使
QuartzSchedulerThread正式進入工作狀態(雖然其構造時就被啟動,但沒有真正開始工作)。
3.總會從jobstore里找出下個應該執行的trigger任務(具體步驟后面解析),然后等待這一時刻到來。(通過不停的比較trigger的時間和當前系統時間)
4. 之后通過jobstore將其對應的jobdetail封裝成一個runShell (具體步驟后面解析)
5. 將runShell交給線程池去執行,runInThread方法,下節會具體講解。
6. 繼續循環尋找下一個執行trigger,依次往復。
因此上面提到過其實在quartz里面,真正做實際工作的調度線程其實就是它。
第五節.SimpleThreadPool的介紹
總體來講,SimpleThreadPool的實現很簡單。
第2節介紹createScheduler過程時,提到第一個步驟便是創建一個線程池,該默認的線程池便是SimpleThreadPool. 跟蹤源代碼可見,最然構建tp實例時未做任何操作,但在實例之后,又調用了SimpleThreadPool.initialize(), 此方法相當於初始配置tp以及啟用它。下面來分析源碼
a.代碼比較簡單,注意一點是創建的線程是WorkerThread,因此看具體工作還需要追蹤到WorkerThread里面。

B.WorkerThread是一個內部類,看下被啟動是具體做什么工作(run方法)。
比較簡單,“一直不停的在等待runable類型的事件的注入,一旦進來,就執行”。

本節和上節可以結合在一起歸納一下,QuartzSchedulerThread一直尋找可執行目標,找到后就交給SimpleThreadPool具體執行, 而相應的SimpleThreadPool的線程們也被設計成隨時等待runnable事件的注入。舉個通俗的例子,QuartzSchedulerThread就是青樓的老媽子,SimpleThreadPool
里的線程就是待接客的姑娘們,老媽子負責不斷尋找/接待客人,然后將目標客戶引給閑置的姑娘, 具體的接待工作由姑娘完成。
現在提出兩個問題,“客人”是如何被找到的? “姑娘”是如何開始接待的? 在下節討論。
6.RAMJobStore的介紹
JobStore負責保存所有配置到調度器里的工作數據:jobs,triggers,calendars等等,“RAM”顧名思義就是利用內存來持久化調度程序信息。這種作業存儲類型最容易配置、構造和 運行,但是當應用程序停止運行時,所有調度信息將被丟失。RAMJobStore是spring+quatz默認的jobstore
在第4節中講解QuartzSchedulerThread的工作原理中多次提到了jobStore,其中有一個步驟便是通過jobStore獲取下一個應該執行的trigger,現在就來具體分析。
A. 利用jobStore獲取下一個需要執行的trigger, 此處調用的是acquireNextTrigger()方法。 注意一點,在這步中一般情況下是先將第一個執行的trigger從treeset中刪除,然后返回它。 treeset是按照觸發是每個trigger最近一次應該執行的時間排序的。 大概原理是這樣的,1.往隊列添加trigger的時候,是以該trigger的下次觸發時間來
確定它在treeset的順序 2.要執行trigger時,是直接將trigger移出treeset. 3.在恰當的時刻,將該trigger按照第一步加入到treeset

B.上面的一點你可能會思考一個問題,這個trigger的下一次是怎么觸發的(因為已經從timeTriggers隊列里刪除了)。
實際上在第四節的代碼講解中有一句注釋的代碼,當時沒有提及,因這代碼前后都有其他邏輯,所以是以注釋的形式來展現的。 在此處說明比較合適。

此方法包含了如下操作, 當然包含了諸多其他內容,此處不展開說明:
根據當前的nextFireTime來實現自我更新, 然后將此trigger重新加入到timeTriggers。
而此方法的返回值bndle在C中詳解。

C. 第四節講到拿到Trigger的下一步是創建一個jobshell丟給線程池去執行。現在看看jobShell是如何創建的。
首先B中 triggerFired方法返回的bndle對象,它是TriggerFiredBundle類型,該類屬性如下,實質上這些數據也是從jobStore那里引用過來的,封裝成一個對象方便后續處理。

創建runshell的數據來源正是基於這個對象的。 創建runshell實例很簡單,對實例的初始化過程主要是由JobRunShell.initialize()方法負責的。具體分析:

runShell創建並初始化之后,是被丟進ThreadPool中執行的,而執行過程就是線程調用runShell的run()方法, 大家可以具體查看,這里由於我整理的文檔涉及
我們項目內容,省略掉這一部分。
文檔至此,你應當比較全面的了解quatz的初始化過程以及調用機制
三. Spring+quatz的參考文檔
以下參考文檔分別從各方面側重介紹了quatz,其中主要參考了《quartz學習筆記》
http://jinnianshilongnian.iteye.com/blog/1902886 詳解spring事件驅動模型
http://www.cnblogs.com/yunxuange/archive/2012/08/28/2660141.html quatz 學習筆記
http://www.cnblogs.com/chanedi/p/4169510.html Quartz的線程池解析
http://blog.csdn.net/luccs624061082/article/details/39288275 Spring 之生命周期機制混合使用
http://www.cnblogs.com/langtianya/archive/2013/05/15/3079109.html quatz詳解
http://seanhe.iteye.com/blog/691835 Spring對Quartz的封裝實現簡單分析及使用注意事項
文章部分引自 http://www.cnblogs.com/surprizeFuture/articles/4564293.html
