取消與關閉(第七章)

取消與關閉

Java中沒有提供任何機制來安全得終止線程,但它提供了中斷(Interruption),這是一種協做機制,可以使一個線程終止另外一個線程當前的工做。 咱們不多但願某個任務、線程或服務當即中止,由於這種當即中止會使共享的數據結構處於不一致的狀態。相反,在編寫任務和服務時可使用一種協做的方式:當須要中止時,它們首先清楚當前正在執行的工做,而後再結束編程

1.任務取消

一個可取消的任務必須擁有取消策略(Cancellation Policy),在這個策略中應詳細定義取消操做的「How」、「When」以及「What」,即其餘代碼如何(How)請求取消任務,任務在什麼時候(When)檢查是否已經請求了取消,以及在響應取消請求時應執行哪些(What)操做。安全

取消任務的方式:數據結構

  1. 設置一個取消標誌,任務在每次迭代執行前首先檢查此標誌以肯定任務是否被取消
  2. 中斷。在任務中若是存在阻塞調用,那麼第一種方法將失效,此時,能夠經過中斷方法取消任務

2.中斷

Thread中的中斷方法:併發

public class Thread{
	public void interrupt(){...}
    public boolean isInterrupted(){...}
    /*靜態的interrupted方法將清除當前線程的中斷狀態,並返回它以前的值,這也是清除中斷狀態的惟一方法*/
    public static boolean interrupted(){...}
}
調用interrupt並不意味着當即中止目標線程正在進行的工做,而只是傳遞了請求中斷的消息。

對中斷操做的正確理解是:它並不會真正地中斷一個正在運行的線程,而只是發出中斷請求,而後由線程在下一個合適的時刻中斷本身函數

並不是全部阻塞調用都能響應中斷操做,wait、sleep、join等將嚴格處理中斷請求,而IO阻塞並不能響應(常規的)中斷請求。this

3.中斷策略

中斷策略規定線程如何解釋某個中斷請求----當發現中斷請求時,應該作哪些工做。 最合理的中斷策略是某種形式的線程級(Thread Level)取消操做或服務級(Service Level)取消操做:儘快推出,在必要時進行清理,同志某個進程全部者該線程已推出。 大多數可阻塞的庫函數都只是拋出InterruptedException做爲中斷響應:儘快推出執行流程,並把中斷消息傳遞給調用者,從而使調用棧中的上層代碼能夠採起進一步的操做。 當調用可中斷的阻塞方法,例如sleep和wait,有兩種策略能夠用於處理InterruptedException:操作系統

  1. 傳遞異常(可能在執行某個 特定於任務的清理後),從而使你的方法也能成爲可中斷的阻塞方法
  2. 恢復中斷狀態,從而使調用棧中的上層代碼可以對其進行處理

經過Future來實現取消: ExecutorService.submit將返回一個Future來描述任務。Future擁有一個cancel方法,該方法帶有一個boolean類型的參數mayInterruptIfRunning,表示操做可否接受中斷,若是爲true而且任務正在某個線程中運行,那麼這個線程能被中斷,若是爲false,那麼意味着「若任務尚未啓動,就不要啓動它」。線程

當嘗試取消某個任務時,不宜直接中斷線程池,由於你並不知道當中斷請求到達時正在運行什麼任務----只能經過任務的Future來實現取消。

4.處理不可中斷的阻塞

並不是全部的可阻塞方法或阻塞機制都能響應中斷,若是一個線程因爲執行同步的Socket I/O或者等待得到內置鎖而阻塞,那麼中斷請求只能設置線程的中斷狀態,除此以外沒有其餘任何做用。對於那些因爲執行不可中斷操做而被阻塞的線程,可使用相似於中斷的手段來中止這些線程,但這要求咱們必須知道線程阻塞的緣由。日誌

5.中止基於線程的服務

應用程序一般會建立擁有多個線程的服務,例如線程池,而且這些服務的生命週期一般比建立它們的方法的生命週期更長。若是應用程序準備退出,那麼這些服務所擁有的線程也須要結束。因爲沒法經過搶佔式的方法來中止線程,所以它們須要自行結束。 正確的封裝原則是:除非擁有某個線程,不然不能對該線程進行操控,例如:中斷線程或者修改線程的優先級等。對於持有線程的服務,只要服務的存在時間大於建立線程的方法的存在時間,那麼就應該提供生命週期方法。code

線程的全部權是不可傳遞的:應用程序能夠擁有服務,服務也能夠擁有工做者線程,但應用程序並不能擁有工做者線程,所以應用程序不能直接中止工做者線程。

6.處理非正常的線程終止

致使線程提早死亡的最主要緣由就是RuntimeException,因爲這些異常表示出現了某種編程錯誤或者其餘不可修復的錯誤,所以它們一般不會被捕獲,它們不會在調用棧中逐層傳遞,而是默認地在控制檯中輸出棧追蹤信息,並終止線程。 除了主動捕獲異常,在Thread API中提供了UncaughtExceptionHandler,它能檢測出某個線程因爲未捕獲的異常而終結的狀況。當一個線程因爲未捕獲異常而退出時,JVM會把這個事件報告給應用程序提供的UncaughtExceptionHandler,若是沒有提供任何異常處理器,那麼默認的行爲是將棧追蹤信息輸出到System.err。

public interface UncaughtExceptionHandler {
	void uncaughtException(Thread t, Throwable e);
}
在運行時間較長的應用程序中,一般會爲全部線程的未捕獲異常指定同一個異常處理器,而且該處理器至少會將異常信息記錄到日誌中。

7.JVM關閉

JVM既能夠正常關閉,也能夠強行關閉。正常關閉的觸發方式包括:

  1. 當最後一個普通線程結束時(線程分爲普通線程和守護線程
  2. 調用了System.exit
  3. 經過其餘特定於平臺的方法關閉時(例如發送了SIGINT信號或鍵入Ctrl-C)

強行關閉JVM的方法: 調用Runtime.halt、在操做系統中kill JVM

1. 關閉鉤子

關閉鉤子(Shutdown Hook)是指經過Runtime.addShutdownHook註冊的但還沒有開的線程。 在正常關閉中,JVM首先調用全部已註冊的關閉鉤子,但JVM並不能保證關閉鉤子的調用順序,在關閉應用程序線程時,若是有線程(包括守護線程)仍然在運行,那麼這些線程接下來將與關閉進程併發執行,當全部的關閉鉤子都執行結束時,若是runFinalizersOnExit爲true,那麼JVM將運行終結器,而後再中止。若是關閉鉤子或終結器沒有執行完成,那麼正常關閉進程「掛起」而且JVM必須被強行關閉。當被強行關閉時,只是關閉JVM,而不會運行關閉鉤子。 關閉鉤子不該該依賴那些可能被應用程序或其餘關閉鉤子關閉的服務,實現這種功能的一種方式是對全部服務使用同一個關閉鉤子而不是每一個服務使用一個不一樣的關閉鉤子。 一個關閉鉤子的例子:

public void start() {
	Runtime.getRuntime().addShutdownHook(new Thread() {
    	public void run() {
        	try {
            	LogService.this.stop();
                catch (InterruptedException ignored) {}
            }
        }
    });
}
相關文章
相關標籤/搜索