線程中斷方法interrupt() 與 cancel()以及Runtime.getRuntime().addShutdownHook()

Runtime.getRuntime().addShutdownHook()
關閉鉤子。當正常關閉時,jvm會調用所有註冊的鉤子函數,其實就是自定義線程,當有多個鉤子時須保證鉤子線程間的線程安全,而且應該儘快退出,因爲這會延遲jvm的關閉時間。
<span style="white-space:pre">	</span>Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                System.out.println("close...");
            }
        });



(一).關於interrupt()
   interrupt()並不直接中斷線程,而是設定一箇中斷標識,然後由程序進行中斷檢查,確定是否中斷。
    1. sleep() &interrupt()
    線程A正在使用sleep()暫停着:Thread.sleep(100000);
   如果要取消他的等待狀態,可以在正在執行的線程裏(比如這裏是B)調用a.interrupt();
   令線程A放棄睡眠操作,這裏a是線程A對應到的Thread實例執行interrupt()時,並不需要獲取Thread實例的鎖定.任何線程在任何時刻,都可以調用其他線程interrupt().當sleep中的線程被調用interrupt()時,就會放棄暫停的狀態.並拋出InterruptedException.丟出異常的,是A線程.
2. wait() & interrupt()
    線程A調用了wait()進入了等待狀態,也可以用interrupt()取消.
   不過這時候要小心鎖定的問題.線程在進入等待區,會把鎖定解除,當對等待中的線程調用interrupt()時(注意是等待的線程調用其自己的interrupt()),會先重新獲取鎖定,再拋出異常.在獲取鎖定之前,是無法拋出異常的.
3. join() & interrupt()
   當線程以join()等待其他線程結束時,一樣可以使用interrupt()取消之.因爲調用join()不需要獲取鎖定,故與sleep()時一樣,會馬上跳到catch塊裏.注意是隨調用interrupt()方法,一定是阻塞的線程來調用其自己的interrupt方法.如在線程a中調用來線程t.join().則a會等t執行完後在執行t.join後的代碼,當在線程b中調用來a.interrupt()方法,則會拋出InterruptedException
4. interrupt()只是改變中斷狀態而已
   interrupt()不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一箇中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait,Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
   如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就將得到異常(該線程必須事先預備好處理此狀況),接着逃離阻塞狀態。
   線程A在執行sleep,wait,join時,線程B調用A的interrupt方法,的確這一個時候A會有InterruptedException異常拋出來.但這其實是在sleep,wait,join這些方法內部會不斷檢查中斷狀態的值,而自己拋出的InterruptedException。
   如果線程A正在執行一些指定的操作時如賦值,for,while,if,調用方法等,都不會去檢查中斷狀態,所以線程A不會拋出InterruptedException,而會一直執行着自己的操作.當線程A終於執行到wait(),sleep(),join()時,才馬上會拋出InterruptedException.
   若沒有調用sleep(),wait(),join()這些方法,或是沒有在線程裏自己檢查中斷狀態自己拋出InterruptedException的話,那InterruptedException是不會被拋出來的.   
 
(二)Java線程中斷的本質和編程原則
   在歷史上,Java試圖提供過搶佔式限制中斷,但問題多多,例如前文介紹的已被廢棄的Thread.stop、Thread.suspend和Thread.resume等。另一方面,出於Java應用代碼的健壯性的考慮,降低了編程門檻,減少不清楚底層機制的程序員無意破壞系統的概率。
   如今,Java的線程調度不提供搶佔式中斷,而採用協作式的中斷。其實,協作式的中斷,原理很簡單,就是輪詢某個表示中斷的標記,我們在任何普通代碼的中都可以實現。例如下面的代碼:
   
    volatile bool isInterrupted;
    //…
    while(!isInterrupted){
        compute();
    }

   但是,上述的代碼問題也很明顯。當compute執行時間比較長時,中斷無法及時被響應。另一方面,利用輪詢檢查標誌變量的方式,想要中斷wait和sleep等線程阻塞操作也束手無策。
   如果仍然利用上面的思路,要想讓中斷及時被響應,必須在虛擬機底層進行線程調度的對標記變量進行檢查。是的,JVM中確實是這樣做的。下面摘自java.lang.Thread的源代碼:
       
 <span style="white-space:pre">	</span>public staticboolean interrupted() {
           return currentThread().isInterrupted(true);
        }
       //…
        private nativeboolean isInterrupted(boolean ClearInterrupted);

   可以發現,isInterrupted被聲明爲native方法,取決於JVM底層的實現。
   實際上,JVM內部確實爲每個線程維護了一箇中斷標記。但應用程序不能直接訪問這個中斷變量,必須通過下面幾個方法進行操作:
  
  public class Thread {
        //設置中斷標記
        public void interrupt(){ ... } 
        //獲取中斷標記的值
        public booleanisInterrupted() { ... }
       //清除中斷標記,並返回上一次中斷標記的值
        public static booleaninterrupted() { ...}  
        ...
    }

   通常情況下,調用線程的interrupt方法,並不能立即引發中斷,只是設置了JVM內部的中斷標記。因此,通過檢查中斷標記,應用程序可以做一些特殊操作,也可以完全忽略中斷。
   你可能想,如果JVM只提供了這種簡陋的中斷機制,那和應用程序自己定義中斷變量並輪詢的方法相比,基本也沒有什麼優勢。
    JVM內部中斷變量的主要優勢,就是對於某些情況,提供了模擬自動“中斷陷入”的機制。
   在執行涉及線程調度的阻塞調用時(例如wait、sleep和join),如果發生中斷,被阻塞線程會“儘可能快的”拋出InterruptedException。因此,我們就可以用下面的代碼框架來處理線程阻塞中斷:
    
    try {
        //wait、sleep或join
       }
    catch(InterruptedException e){
        //某些中斷處理工作 
      }

   所謂“儘可能快”,我猜測JVM就是在線程調度調度的間隙檢查中斷變量,速度取決於JVM的實現和硬件的性能。   
   然而,對於某些線程阻塞操作,JVM並不會自動拋出InterruptedException異常。例如,某些I/O操作和內部鎖操作。對於這類操作,可以用其他方式模擬中斷:
    1)java.io中的異步socket I/O
   讀寫socket的時候,InputStream和OutputStream的read和write方法會阻塞等待,但不會響應java中斷。不過,調用Socket的close方法後,被阻塞線程會拋出SocketException異常。
    2)利用Selector實現的異步I/O
   如果線程被阻塞於Selector.select(在java.nio.channels中),調用wakeup方法會引起ClosedSelectorException異常。
    3)鎖獲取
   如果線程在等待獲取一個內部鎖,我們將無法中斷它。但是,利用Lock類的lockInterruptibly方法,我們可以在等待鎖的同時,提供中斷能力。
   另外,在任務與線程分離的框架中,任務通常並不知道自身會被哪個線程調用,也就不知道調用線程處理中斷的策略。所以,在任務設置了線程中斷標記後,並不能確保任務會被取消。因此,有以下兩條編程原則:
    1)除非你知道線程的中斷策略,否則不應該中斷它。
       這條原則告訴我們,不應該直接調用Executer之類框架中線程的interrupt方法,應該利用諸如Future.cancel的方法來取消任務。
   2)任務代碼不該猜測中斷對執行線程的含義。
       這條原則告訴我們,一般代碼遇在到InterruptedException異常時,不應該將其捕獲後“吞掉”,而應該繼續向上層代碼拋出。
   總之,Java中的非搶佔式中斷機制,要求我們必須改變傳統的搶佔式中斷思路,在理解其本質的基礎上,採用相應的原則和模式來編程。
 
(三) interrupt() 與cancel()的區別
   兩者實際上都是中斷線程,但是後者更安全、有條理和高效,其原因跟推薦使用Executor而不直接使用Thread類是一致的。所以結合上面講到的原則,我們應儘量採用cancel()方法,調用線程管理器ExecutorService接口的submit(Runnable task)方法會返回一個Future<?>對象,然後調用Future.cancel()的方法來取消任務,並返回一個boolean值。
發佈了42 篇原創文章 · 獲贊 3 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章