關於線程中斷的總結


Core Java中有這樣一句話:"沒有任何語言方面的需求要求一個被中斷的程序應該終止。中斷一個線程只是為了引起該線程的注意,被中斷線程可以決定如何應對中斷 "

中斷是一種協作機制。當一個線程中斷另一個線程時,被中斷的線程不一定要立即停止正在做的事情。相反,中斷是禮貌地請求另一個線程在它願意並且方便的時候停止它正在做的事情。有些方法,例如 Thread.sleep(),很認真地對待這樣的請求,但每個方法不是一定要對中斷作出響應。對於中斷請求,不阻塞但是仍然要花較長時間執行的方法可以輪詢中斷狀態,並在被中斷的時候提前返回。 您可以隨意忽略中斷請求,但是這樣做的話會影響響應。

中斷的協作特性所帶來的一個好處是,它為安全地構造可取消活動提供更大的靈活性。我們很少希望一個活動立即停止;如果活動在正在進行更新的時候被取消,那么程序數據結構可能處於不一致狀態。中斷允許一個可取消活動來清理正在進行的工作,恢復不變量,通知其他活動它要被取消,然后才終止。




線程中斷在線程生命周期中的作用:

線程狀態 :
 Java虛擬機將線程運行過程分成四種狀態 :
 (1) New 新生;(2) Runnable 可運行;(3) Blocked 阻塞;(4) Dead 死亡。

值得注意的是: 線程的可運行狀態並不代表線程一定在運行(runnable != running ) 。 
大家都知道:所有現代桌面和服務器操作系統都使用了搶占式的線程調度策略  一旦線程開始執行,並不是總是保持持續運行狀態的。當系統分給它的時間片(非常小的運行時間單位)用完以后,不管程序有沒有執行完,線程被強制放棄 CPU,進入就緒狀態,直到下次被調度后開始繼續執行。
也就是說, Runnable可運行狀態的線程處於兩種可能的情況下:
(1)占用CPU運行中,
(2)等待調度的就緒狀態。
這里要聲明一下:處於等待調度的就緒狀態線程和處於阻塞的線程是完全不同的
就緒的線程是因為時間片用完而放棄CPU,其隨時都有可能再次獲得CPU而運行,這一切取決於分時OS的線程調度策略。
 在很多操作系統的專業術語中,這種因時間片用完而被剝奪CPU的情況我們叫做線程中斷 。注意這和我們下面要將得中斷線程是兩個完全不同的概念。事實上,我們不可能通過應用程序來控制CPU的線程中斷,除非我們能夠自由調用OS的內核。

http://blog.sina.com.cn/s/blog_7a351012010166si.html

如今,Java的線程調度不提供搶占式中斷,而采用協作式的中斷。其實,協作式的中斷,原理很簡單,就是輪詢某個表示中斷的標記
另一方面,利用輪詢檢查標志變量的方式,想要中斷wait和sleep等線程阻塞操作也束手無策。 如果仍然利用上面的思路,要想讓中斷及時被響應,必須在虛擬機底層進行線程調度的對標記變量進行檢查。是的,JVM中確實是這樣做的。下面摘自java.lang.Thread的源代碼:

代碼如下:

public static boolean interrupted() {
return currentThread().isInterrupted(true);
private native boolean isInterrupted(boolean ClearInterrupted);

可以發現,isInterrupted被聲明為native方法,取決於JVM底層的實現。 實際上,JVM內部確實為每個線程維護了一個中斷標記。但應用程序不能直接訪問這個中斷變量,必須通過下面幾個方法進行操作:
代碼如下:

public class Thread {
//設置中斷標記
public void interrupt() { ... }
//獲取中斷標記的值
public boolean isInterrupted() { ... }
//清除中斷標記,並返回上一次中斷標記的值
public static boolean interrupted() { ... }
  }

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

代碼如下:

try {
//wait、sleep或join
} catch(InterruptedException e) {
//某些中斷處理工作
}

所謂“盡可能快”,我猜測JVM就是在線程調度調度的間隙檢查中斷變量,速度取決於JVM的實現和硬件的性能。

http://www.cnblogs.com/softidea/p/4411608.html

通常,中斷的使用場景有以下幾個:
點擊某個桌面應用中的取消按鈕時;
某個操作超過了一定的執行時間限制需要中止時;
多個線程做相同的事情,只要一個線程成功其它線程都可以取消時;
一組線程中的一個或多個出現錯誤導致整組都無法繼續時;
當一個應用或服務需要停止時。

http://www.cnblogs.com/softidea/p/4411602.html
http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java

這樣的情景您也許並不陌生:您在編寫一個測試程序,程序需要暫停一段時間,於是調用Thread.sleep()。但是編譯器或 IDE 報錯說沒有處理檢查到的InterruptedExceptionInterruptedException 是什么呢,為什么必須處理它?

對於 InterruptedException,一種常見的處理方式是 “生吞(swallow)” 它 —— 捕捉它,然后什么也不做(或者記錄下它,不過這也好不到哪去)—— 就像后面的 清單 4 一樣。不幸的是,這種方法忽略了這樣一個事實:這期間可能發生中斷,而中斷可能導致應用程序喪失及時取消活動或關閉的能力。

阻塞方法

當一個方法拋出 InterruptedException 時,它不僅告訴您它可以拋出一個特定的檢查異常,而且還告訴您其他一些事情。例如,它告訴您它是一個阻塞(blocking)方法,如果您響應得當的話,它將嘗試消除阻塞並盡早返回。

阻塞方法不同於一般的要運行較長時間的方法。一般方法的完成只取決於它所要做的事情,以及是否有足夠多可用的計算資源(CPU 周期和內存)。而阻塞方法的完成還取決於一些外部的事件,例如計時器到期,I/O 完成,或者另一個線程的動作(釋放一個鎖,設置一個標志,或者將一個任務放在一個工作隊列中)。一般方法在它們的工作做完后即可結束,而阻塞方法較難於預測,因為它們取決於外部事件。阻塞方法可能影響響應能力,因為難於預測它們何時會結束。

阻塞方法可能因為等不到所等的事件而無法終止,因此令阻塞方法可取消 就非常有用(如果長時間運行的非阻塞方法是可取消的,那么通常也非常有用)。可取消操作是指能從外部使之在正常完成之前終止的操作。由 Thread 提供並受 Thread.sleep() 和 Object.wait() 支持的中斷機制就是一種取消機制;它允許一個線程請求另一個線程停止它正在做的事情。當一個方法拋出 InterruptedException 時,它是在告訴您,如果執行該方法的線程被中斷,它將嘗試停止它正在做的事情而提前返回,並通過拋出 InterruptedException 表明它提前返回。 行為良好的阻塞庫方法應該能對中斷作出響應並拋出 InterruptedException,以便能夠用於可取消活動中,而不至於影響響應。

線程中斷

每個線程都有一個與之相關聯的 Boolean 屬性,用於表示線程的中斷狀態(interrupted status)。中斷狀態初始時為 false;當另一個線程通過調用 Thread.interrupt() 中斷一個線程時,會出現以下兩種情況之一。如果那個線程在執行一個低級可中斷阻塞方法,例如Thread.sleep() Thread.join() 或 Object.wait(),那么它將取消阻塞並拋出 InterruptedException。否則, interrupt() 只是設置線程的中斷狀態。 在被中斷線程中運行的代碼以后可以輪詢中斷狀態,看看它是否被請求停止正在做的事情。中斷狀態可以通過Thread.isInterrupted() 來讀取,並且可以通過一個名為 Thread.interrupted() 的操作讀取和清除。

中斷是一種協作機制。當一個線程中斷另一個線程時,被中斷的線程不一定要立即停止正在做的事情。相反,中斷是禮貌地請求另一個線程在它願意並且方便的時候停止它正在做的事情。有些方法,例如 Thread.sleep(),很認真地對待這樣的請求,但每個方法不是一定要對中斷作出響應。對於中斷請求,不阻塞但是仍然要花較長時間執行的方法可以輪詢中斷狀態,並在被中斷的時候提前返回。 您可以隨意忽略中斷請求,但是這樣做的話會影響響應。

中斷的協作特性所帶來的一個好處是,它為安全地構造可取消活動提供更大的靈活性。我們很少希望一個活動立即停止;如果活動在正在進行更新的時候被取消,那么程序數據結構可能處於不一致狀態。中斷允許一個可取消活動來清理正在進行的工作,恢復不變量,通知其他活動它要被取消,然后才終止。

 

處理 InterruptedException

如果拋出 InterruptedException 意味着一個方法是阻塞方法,那么調用一個阻塞方法則意味着您的方法也是一個阻塞方法,而且您應該有某種策略來處理 InterruptedException。通常最容易的策略是自己拋出 InterruptedException,如清單 1 中 putTask() 和 getTask() 方法中的代碼所示。 這樣做可以使方法對中斷作出響應,並且只需將 InterruptedException 添加到 throws 子句。

清單 1. 不捕捉 InterruptedException,將它傳播給調用者
public class TaskQueue {
    private static final int MAX_TASKS = 1000;

    private BlockingQueue<Task> queue 
        = new LinkedBlockingQueue<Task>(MAX_TASKS);

    public void putTask(Task r) throws InterruptedException { 
        queue.put(r);
    }

    public Task getTask() throws InterruptedException { 
        return queue.take();
    }
}

有時候需要在傳播異常之前進行一些清理工作。在這種情況下,可以捕捉 InterruptedException,執行清理,然后拋出異常。清單 2 演示了這種技術,該代碼是用於匹配在線游戲服務中的玩家的一種機制。 matchPlayers() 方法等待兩個玩家到來,然后開始一個新游戲。如果在一個玩家已到來,但是另一個玩家仍未到來之際該方法被中斷,那么它會將那個玩家放回隊列中,然后重新拋出 InterruptedException,這樣那個玩家對游戲的請求就不至於丟失。

清單 2. 在重新拋出 InterruptedException 之前執行特定於任務的清理工作
public class PlayerMatcher {
    private PlayerSource players;

    public PlayerMatcher(PlayerSource players) { 
        this.players = players; 
    }

    public void matchPlayers() throws InterruptedException { 
        try {
             Player playerOne, playerTwo;
             while (true) {
                 playerOne = playerTwo = null;
                 // Wait for two players to arrive and start a new game
                 playerOne = players.waitForPlayer(); // could throw IE
                 playerTwo = players.waitForPlayer(); // could throw IE
                 startNewGame(playerOne, playerTwo);
             }
         }
         catch (InterruptedException e) {  
             // If we got one player and were interrupted, put that player back
             if (playerOne != null)
                 players.addFirst(playerOne);
             // Then propagate the exception
             throw e;
         }
    }
}

不要生吞中斷

有時候拋出 InterruptedException 並不合適,例如當由 Runnable 定義的任務調用一個可中斷的方法時,就是如此。在這種情況下,不能重新拋出 InterruptedException,但是您也不想什么都不做。當一個阻塞方法檢測到中斷並拋出 InterruptedException 時,它清除中斷狀態。如果捕捉到 InterruptedException 但是不能重新拋出它,那么應該保留中斷發生的證據,以便調用棧中更高層的代碼能知道中斷,並對中斷作出響應。該任務可以通過調用 interrupt() 以 “重新中斷” 當前線程來完成,如清單 3 所示。至少,每當捕捉到 InterruptedException並且不重新拋出它時,就在返回之前重新中斷當前線程。

清單 3. 捕捉 InterruptedException 后恢復中斷狀態
public class TaskRunner implements Runnable {
    private BlockingQueue<Task> queue;

    public TaskRunner(BlockingQueue<Task> queue) { 
        this.queue = queue; 
    }

    public void run() { 
        try {
             while (true) {
                 Task task = queue.take(10, TimeUnit.SECONDS);
                 task.execute();
             }
         }
         catch (InterruptedException e) { 
             // Restore the interrupted status
             Thread.currentThread().interrupt();
         }
    }
}

處理 InterruptedException 時采取的最糟糕的做法是生吞它 —— 捕捉它,然后既不重新拋出它,也不重新斷言線程的中斷狀態。對於不知如何處理的異常,最標准的處理方法是捕捉它,然后記錄下它,但是這種方法仍然無異於生吞中斷,因為調用棧中更高層的代碼還是無法獲得關於該異常的信息。(僅僅記錄 InterruptedException 也不是明智的做法,因為等到人來讀取日志的時候,再來對它作出處理就為時已晚了。) 清單 4 展示了一種使用得很廣泛的模式,這也是生吞中斷的一種模式:

清單 4. 生吞中斷 —— 不要這么做
// Don't do this 
public class TaskRunner implements Runnable {
    private BlockingQueue<Task> queue;

    public TaskRunner(BlockingQueue<Task> queue) { 
        this.queue = queue; 
    }

    public void run() { 
        try {
             while (true) {
                 Task task = queue.take(10, TimeUnit.SECONDS);
                 task.execute();
             }
         }
         catch (InterruptedException swallowed) { 
             /* DON'T DO THIS - RESTORE THE INTERRUPTED STATUS INSTEAD */
         }
    }
}

如果不能重新拋出 InterruptedException,不管您是否計划處理中斷請求,仍然需要重新中斷當前線程,因為一個中斷請求可能有多個 “接收者”。標准線程池 (ThreadPoolExecutor)worker 線程實現負責中斷,因此中斷一個運行在線程池中的任務可以起到雙重效果,一是取消任務,二是通知執行線程線程池正要關閉。如果任務生吞中斷請求,則 worker 線程將不知道有一個被請求的中斷,從而耽誤應用程序或服務的關閉。

http://www.ibm.com/developerworks/cn/java/j-jtp05236.html

 

 

 

 

 

 

 

 

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM