1. 程式人生 > >部署 Job (第三部分)

部署 Job (第三部分)

5. 易失性、永續性和可恢復性

這三個屬性有些類似的,由於它們影響的都是 Job 的執行時行為。我們下面依次討論它們。
·Job 的易失性

一個易失性的 Job 是在程式關閉之後不會被持久化。一個 Job 是通過呼叫 JobDetail 的 setVolatility(true) 被設定為易失性的。

當你需要持久化 Job 時不應使用 RamJobStore

RamJobStore 使用的是非永久性儲存器,所有關於 Job 和 Trigger 的資訊會在程式關閉之後丟失。儲存 Job 到 RamJobStore 有效的使得它們是易失性的。假如你需要讓你的 Job 資訊在程式重啟之後仍能保留下來,你就該考慮另一種 JobStore 型別,比如 JobStoreTX 或者 JobStoreCMT。它們會在第六章“作業儲存與持久化”中講到。

Job 易失性的預設值是 false.
·Job 永續性
一個持久的 Job 是它應該保持在  JobStore 中的,甚至是在沒有任何 Trigger 去觸發它的時候。我們說,你設定了一個單次觸發的 Trigger,觸發之後它就變成了 STATE_COMPLETE 狀態。Job 執行一次後就不再被觸發了,這個 Trigger 部署之後只為了執行一次。這個 Trigger 指向的 Job 現在成了一個孤兒 Job,因為不再有任何 Trigger 與之相關聯了。
假如你設定一個 Job 為連續性的,即使它成了孤兒 Job 也不會從 JobStore 移除掉。這樣可以保證在將來,無論何時你的程式決定為這個 Job 增加另一個 Trigger 都是可用的。假如呼叫了 JobDetail 的 setDurability(false) 方法,那麼在所有的觸發器觸發之後 Job 將從 JobStore 中移出。連續性的預設值是 false。因此,Job 和 Trigger 的預設行為是:當 Trigger 完成了所有的觸發、Job 在沒有 Trigger 與之關聯時它們就會從 JobStore 中移除。
·Job 的可恢復性


當一個 Job 還在執行中,Scheduler 經歷了一次非預期的關閉,在 Scheduler 重啟之後可恢復的 Job 還會再次被執行。這個 Job 會再次重頭開始執行。Scheduler 是沒法知道在程式停止的時候 Job 執行到了哪個位置,因此必須重新開始再執行一遍。
要設定 Job 為可恢復性,用下面的方法:

  1. public void setRequestsRecovery(boolean shuldRecover); 

public void setRequestsRecovery(boolean shuldRecover);
預設時,這個值為 false,Scheduler 不會試著去恢復 job 的。
·從 Scheduler 中移除 Job


你可用多種方式移除已部署的 Job。一種方式是移除所有與這個 Job 相關聯的 Trigger;如果這個 Job 是非永續性的,它將會從 Scheduler 中移出。一個更簡單直接的方式是使用 deleteJob() 方法:

  1. public boolean deleteJob(String jobName, String groupName)  
  2. throws SchedulerException; 

public boolean deleteJob(String jobName, String groupName) throws SchedulerException;
·中斷 Job

有時候需要能中斷一個 Job,尤其是對於一個長時間執行的 Job。例如,假定你有一個 Job 執行過程要花費一個小時,你發現在 5 分鐘的時候因某個非受控的錯誤被中斷需要接著執行。你或許也會中斷 Job,修復問題,然後又繼續執行。
Quartz 包括一個介面叫做 org.quartz.InterruptableJob,它擴充套件了普通的 Job 介面並提供了一個 interrupt() 方法:

  1. public void interrupt() throws
  2. UnableToInterruptJobException; 

public void interrupt() throws UnableToInterruptJobException;
可以提供 Job 部署時所用的 Job 的名稱和組名呼叫 Scheduler 的 interrupte() 方法:

  1. public boolean interrupt(SchedulingContext ctxt, String  
  2. jobName, String groupName) throws
  3. UnableToInterruptJobException; 

public boolean interrupt(SchedulingContext ctxt, String jobName, String groupName) throws UnableToInterruptJobException;
程式碼 4.8 就是一個叫做 ChedkForInterruptJob 的 InterruptableJob 例子。
Scheduler 接著會呼叫你的 Job 的 interrupt() 方法。這時就取決於你和你的 Job 決定如何中斷 Job 了。Quartz 有幾種如何處理中斷的方式,程式碼 4.8 中提供的是比較通用的。Quartz 框架可向 Job 發出中斷請求的訊號,但此時是你在控制著 Job,因此需要你為中斷訊號作出響應。
程式碼 4.8. InterrupableJob 能用來決定是否呼叫了 Scheduler 的 interrupte()

  1. public class CheckForInterruptJob implements InterruptableJob {  
  2. static Log logger = LogFactory.getLog(CheckForInterruptJob.class);  
  3. private boolean jobInterrupted = false;  
  4. private int counter = 5;  
  5. private boolean jobFinished = false;  
  6. public void interrupt() throws UnableToInterruptJobException {  
  7.           jobInterrupted = true;  
  8.      }  
  9. public void execute(JobExecutionContext context)  
  10. throws JobExecutionException {  
  11. while (!jobInterrupted && !jobFinished) {  
  12. // Perform a small amount of processing
  13.                logger.info("Processing the job");  
  14.                counter--;  
  15. if (counter <= 0) {  
  16.                     jobFinished = true;  
  17.                     }  
  18. // Sleep and wait for 3 seconds
  19. try {  
  20.                     Thread.sleep(3000);  
  21.                } catch (InterruptedException e) {  
  22. // do nothing
  23.                }  
  24.           }  
  25. if (jobFinished) {  
  26.                logger.info("Job finished without interrupt");  
  27.           } else if (jobInterrupted) {  
  28.                logger.info("Job was interrupted");  
  29.           }  
  30.      }  

public class CheckForInterruptJob implements InterruptableJob { static Log logger = LogFactory.getLog(CheckForInterruptJob.class); private boolean jobInterrupted = false; private int counter = 5; private boolean jobFinished = false; public void interrupt() throws UnableToInterruptJobException { jobInterrupted = true; } public void execute(JobExecutionContext context) throws JobExecutionException { while (!jobInterrupted && !jobFinished) { // Perform a small amount of processing logger.info("Processing the job"); counter--; if (counter <= 0) { jobFinished = true; } // Sleep and wait for 3 seconds try { Thread.sleep(3000); } catch (InterruptedException e) { // do nothing } } if (jobFinished) { logger.info("Job finished without interrupt"); } else if (jobInterrupted) { logger.info("Job was interrupted"); } } }
·框架所提供的 Job
Quartz 框架提供了幾個可在你的應用程式中輕鬆使用的 Job,表 4.1 列出了那種 Job 及用法
表 4.1. Quartz 框架所提供的 Job

Job 類
Job 用法

org.quartz.jobs.FileScanJob
檢查某個指定檔案是否變化,並在文
件被改變時通知到相應監聽器的 Job

org.quartz.jobs.FileScanListener
在檔案被修改後通知 FileScanJob 的監聽器

org.quartz.jobs.NativeJob
用來執行本地程式(如 windows 下 .exe 檔案) 的 Job

org.quartz.jobs.NoOpJob
什麼也不做,但用來測試監聽器不是很有用的。
一些使用者甚至僅僅用它來導致一個監聽器的執行

org.quartz.jobs.ee.mail.SendMailJob
使用 JavaMail API 傳送 e-mail 的 Job

org.quartz.jobs.ee.jmx.JMXInvokerJob
呼叫 JMX bean 上的方法的 Job

org.quartz.jobs.ee.ejb.EJBInvokerJob
用來呼叫 EJB 上方法的 Job

6. 快速瞭解Java 執行緒
本章很短小卻很有必要暫時從對 Quartz 框架的討論中轉移到這個話題來。執行緒在 Quartz 框架中扮演著一個很重要的角色。要是沒有執行緒,Java(以及 Quartz) 就不得不使用重量級的程序來執行併發的任務(Job,對於 Quartz 來說)。這個材料對於已經理解了 Java 中執行緒如何工作的人來說是很基本的。假如你是這類人,請寬容一下。假如你還沒有機會去學習有關 Java 執行緒的知識,現在是個相當好的時機去快速瞭解它。儘管主要是關注於 Java 執行緒的討論,我們在後面部份會由此進一步討論執行緒在 Quartz 是如何運用的。
·Java 中的執行緒
執行緒允許程式同一時間做很多工,至少,看起來那些任務是併發執行的。本章的討論不考慮並行處理的情形,在任一特定時刻僅有一個執行緒在執行,但是 CPU 給每個執行緒一小片時間執行(通過時間片)然後來回線上程間快速的切換。這就是我們所看到的多執行緒執行的樣子。

Java 語言使用 Thread 類內建了對執行緒的支援。當一個執行緒被通知執行,該執行緒的 run() 方法就被執行。正是因為你建立了一個執行緒例項並且呼叫的是 start() 方法,所以並不意意味著相應的 run() 方法會得到立即執行;這個執行緒例項必須等待,直到 JVM 告訴它可以執行。

·執行緒的生命週期

執行緒在它的生命週期中會是幾種可能的狀態中的一種。在同一時刻只能處於一種狀態,這些狀態是由 JVM 指示的,而不是作業系統。執行緒狀態列示如下:

    ·新建
    ·可執行
    ·阻塞
    ·等待中
    ·指定時間的等待
    ·結束

當一個新的執行緒被建立後,它就被指定為新建狀態。執行緒在此狀態時不會被分配任何系統資源。要使執行緒轉到可執行狀態,必須呼叫 start() 方法。

當呼叫了新建執行緒的 start() 方法,執行緒進入到就緒狀態並被認為是執行中的。這不代表這個執行緒實際正在執行指令。即使執行緒的狀態被設定為可執行,已安排去執行,它或許不得不等到其他執行中的執行緒結束或被臨時掛起。JVM 來決定哪個執行緒該獲得下次執行的機會。JVM 決定下個執行緒的行為依賴於數個因素,包括作業系統、特定的 JVM 實現和執行緒優先順序及其他因素。圖 4.4 顯示了 Java 執行緒的生命週期。

Java 執行緒的生命週期


阻塞和等待狀態對於本書來說討論的餘地不大,也是要好分幾個主題才能講清,我們在此不必對此深入。假如你想要關於這些主題更多資訊的話,你可以看 Sun 站點的 Java 指南關於執行緒的內容,http://java.sun.com/docs/books/tutorial/essential/threads/index.html

·程序和執行緒
每一個 Java 程式被指派一個程序。程式接著把任務分派到執行緒中。甚至是你在寫一個僅包含一個 main() 方法的 "Hello, World" 程式,程式仍然要使用執行緒,雖然只有一個。
有時候,所謂“輕量級程序”指的就是執行緒。這與實際的作業系統程序“重量級”一詞形成對比。假如你正在使用 Windows 系統,你可以在工作管理員中看到執行著的重量級程序,但是你看不到執行緒。圖 4.5 描繪了一個 Java 程式中的多執行緒如何操作。

執行在一個程式中的兩個執行緒

從圖 4.5 中看到,執行緒通常不會併發著執行的。這裡還是先排除多 CPU 和並行處理的情況的話,同一時刻只能有一個執行緒才能得到 CPU 的參與執行。每一個 JVM 和作業系統對此處理方式可能有所不同,然而,有時,一個執行緒可能要執行完成後,另一執行緒才能接著執行。這就是所知的“非搶先式”。其他的,一個執行緒中途被打斷來讓其他執行緒做它們的事。這被稱做“搶先式”。一般的,執行緒必須完成執行後另一執行緒才能獲得 CPU,這種對 CPU 資源的竟爭通常是基於執行緒優先順序的。
·理解和使用執行緒優先順序
我們已多次提到,可執行執行緒是否能獲得下次執行機會是由 JVM 來決定的。JVM 支援的一種知名的排程方案“固定優先順序排程”,它排程執行緒是基於它們與其他可執行執行緒優先順序相比較而定的。
優先順序是一個指定給新執行緒的整數值,它還應參考建立它的父執行緒的優先順序。這個值從 Thread.MIN_PRIORITY (等於 1),到 Thread.MAX_PRIORITY (等於 10)。這些常量值在 java.lang.Thread 類中有定義。
這個值越大 (最大到 MAX_PRIORITY),所具有的優先順序就越高。除非特別指定,Java 程式中多數剛建立的執行緒運線在 Thread.NORMAL_PRIORITY (恰好是個中值 5) 級別上。你可能用 setPriority() 方法來修改執行緒的優先順序。
通常,一個執行著的執行緒持續執行直到下面情況中的一個:
    ·執行緒讓出 CPU (可能是呼叫了它的 sleep() 方法、yield() 方法或者是 Object.wait() 方法)。
    ·執行緒的 run() 結束了。
    ·OS 支援時間片的話,它的時間到了
    ·另一高優先順序的執行緒變為了可執行狀態
·守護執行緒
守護執行緒有時指的是服務執行緒,因為他們執行在很低的優先級別上(在後臺),完成些非緊急但卻本質的任務。例如,Java 的垃圾回收器就是一個守護執行緒的例子。這個執行緒執行在後臺,查詢並回收程式不再使用的記憶體。
你可以通過傳遞 true 給 setDaemon() 方法使執行緒成為守護執行緒。不然,這個執行緒就是一個使用者執行緒。你僅能線上程啟動之前把它設定為守護執行緒。守護執行緒有點特別的就是假如只有守護執行緒在執行而沒有活著的非守護執行緒,此時 JVM 仍然是存在的。
· Java 執行緒組和執行緒池
Java 的 ThreadGroup 由 java.lang.ThreadGroup 類實現的,描繪的是一組行為如單一實體般的執行緒。每一個 Java 執行緒都是執行緒組的成員。在一個執行緒組中,你可以停止或啟動所有的執行緒組成員。你還可以設定執行緒優先順序和執行其他執行緒的通用功能。執行緒組對於構建像 Quartz 那樣的多執行緒程式是非常有用的。
當一個 Java 程式啟動後,它就建立了一個叫做 main 的 ThreadGrop。除非特別指定,所有建立的執行緒都會成為這個組的成員。當一個執行緒被指定到某個 ThreadGroup 時,它才會被改變。
·Java 執行緒池 (ThreadPool)

Java 1.5 引入一個新的概念到 Java 語言中,稱之執行緒池。第一眼看來,這有些像是執行緒組,但是它實際是用於不同目的的。儘量多個執行緒能從屬於一個相同的 ThreadGroup 中,組是享用不到典型的池化資源帶來的好處的。這就是說,執行緒組僅僅是用於對成員的組織。
執行緒池是可共享的資源,是一個能被用來執行任務的可管理的執行緒集合。執行緒池(通常講的資源池) 比非池化資源提供了幾點好處。首先也是首要的,資源池通過減少過度的物件建立改善了效能。假如你例項化十個執行緒,並重復的使用它們,而不是每次需要一個都建立一個新的執行緒,你將會改善程式的效能。這個概念在 Java 和 J2EE 領域中比比皆是。另外的優點包括能限制資源的數量,這有助於程式的穩定性和可擴充套件性。這是一個非常有意義的特徵,而不管你所用的是什麼語言,或是什麼程式。