一個線程在未正常結束之前, 被強制終止是很危險的事情. 因為它可能帶來完全預料不到的嚴重后果比如會帶着自己所持有的鎖而永遠的休眠,遲遲不歸還鎖等。 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了
那么不能直接把一個線程搞掛掉, 但有時候又有必要讓一個線程死掉, 或者讓它結束某種等待的狀態 該怎么辦呢?一個比較優雅而安全的做法是:使用等待/通知機制或者給那個線程一個中斷信號, 讓它自己決定該怎么辦。
等待/通過機制在另一篇博客中詳細的介紹了。這里我們理解線程中斷的使用場景和使用時的注意事項,最后使用Demo來理解。
中斷線程的使用場景:
在某個子線程中為了等待一些特定條件的到來, 你調用了Thread.sleep(10000), 預期線程睡10秒之后自己醒來, 但是如果這個特定條件提前到來的話, 來通知一個處於Sleep的線程。又比如說.線程通過調用子線程的join方法阻塞自己以等待子線程結束, 但是子線程運行過程中發現自己沒辦法在短時間內結束, 於是它需要想辦法告訴主線程別等我了. 這些情況下, 就需要中斷.
斷是通過調用Thread.interrupt()方法來做的. 這個方法通過修改了被調用線程的中斷狀態來告知那個線程, 說它被中斷了. 對於非阻塞中的線程, 只是改變了中斷狀態, 即Thread.isInterrupted()將返回true; 對於可取消的阻塞狀態中的線程, 比如等待在這些函數上的線程, Thread.sleep(), Object.wait(), Thread.join(), 這個線程收到中斷信號后, 會拋出InterruptedException, 同時會把中斷狀態置回為true.但調用Thread.interrupted()會對中斷狀態進行復位。
對非阻塞中的線程中斷的Demo:
public class Thread3 extends Thread{ public void run(){ while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println("Someone interrupted me."); } else{ System.out.println("Thread is Going..."); } } } public static void main(String[] args) throws InterruptedException { Thread3 t = new Thread3(); t.start(); Thread.sleep(3000); t.interrupt(); } }
分析如上程序的結果:
在main線程sleep的過程中由於t線程中isInterrupted()為false所以不斷的輸出”Thread is going”。當調用t線程的interrupt()后t線程中isInterrupted()為true。此時會輸出Someone interrupted me.而且線程並不會因為中斷信號而停止運行。因為它只是被修改一個中斷信號而已。
首先我們看看interrupt究竟在干什么。
當我們調用t.interrput()的時候,線程t的中斷狀態(interrupted status) 會被置位。我們可以通過Thread.currentThread().isInterrupted() 來檢查這個布爾型的中斷狀態。
在Core Java中有這樣一句話:”沒有任何語言方面的需求要求一個被中斷的程序應該終止。中斷一個線程只是為了引起該線程的注意,被中斷線程可以決定如何應對中斷 “。好好體會這句話的含義,看看下面的代碼:
//Interrupted的經典使用代碼 public void run(){ try{ .... while(!Thread.currentThread().isInterrupted()&& more work to do){ // do more work; } }catch(InterruptedException e){ // thread was interrupted during sleep or wait } finally{ // cleanup, if required } }
很顯然,在上面代碼中,while循環有一個決定因素就是需要不停的檢查自己的中斷狀態。當外部線程調用該線程的interrupt 時,使得中斷狀態置位即變為true。這是該線程將終止循環,不在執行循環中的do more work了。
這說明: interrupt中斷的是線程的某一部分業務邏輯,前提是線程需要檢查自己的中斷狀態(isInterrupted())。
但是當線程被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時。調用它的interrput()方法。可想而知,沒有占用CPU運行的線程是不可能給自己的中斷狀態置位的。這就會產生一個InterruptedException異常。
/* * 如果線程被阻塞,它便不能核查共享變量,也就不能停止。這在許多情況下會發生,例如調用 * Object.wait()、ServerSocket.accept()和DatagramSocket.receive()時,他們都可能永 * 久的阻塞線程。即使發生超時,在超時期滿之前持續等待也是不可行和不適當的,所以,要使 * 用某種機制使得線程更早地退出被阻塞的狀態。很不幸運,不存在這樣一種機制對所有的情況 * 都適用,但是,根據情況不同卻可以使用特定的技術。使用Thread.interrupt()中斷線程正 * 如Example1中所描述的,Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法 * 實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退出阻塞的狀態。更 * 確切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那么, * 它將接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態。因此, * 如果線程被上述幾種方法阻塞,正確的停止線程方式是設置共享變量,並調用interrupt()(注 * 意變量應該先設置)。如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就 * 將得到異常(該線程必須事先預備好處理此狀況),接着逃離阻塞狀態。在任何一種情況中,最 * 后線程都將檢查共享變量然后再停止。下面示例描述了該技術。 * */ package Concurrency.Interrupt; class Example3 extends Thread { volatile boolean stop = false; public static void main(String args[]) throws Exception { Example3 thread = new Example3(); System.out.println("Starting thread..."); thread.start(); Thread.sleep(3000); System.out.println("Asking thread to stop..."); /* * 如果線程阻塞,將不會檢查此變量,調用interrupt之后,線程就可以盡早的終結被阻 * 塞狀 態,能夠檢查這一變量。 * */ thread.stop = true; /* * 這一方法實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退 * 出阻 塞的狀態 * */ thread.interrupt(); Thread.sleep(3000); System.out.println("Stopping application..."); System.exit(0); } public void run() { while (!stop) { System.out.println("Thread running..."); try { Thread.sleep(2000); } catch (InterruptedException e) { // 接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態 System.out.println("Thread interrupted..."); } } System.out.println("Thread exiting under request..."); } } /* * 把握幾個重點:stop變量、run方法中的sleep()、interrupt()、InterruptedException。串接起 * 來就是這個意思:當我們在run方法中調用sleep(或其他阻塞線程的方法)時,如果線程阻塞的 * 時間過長,比如10s,那在這10s內,線程阻塞,run方法不被執行,但是如果在這10s內,stop被 * 設置成true,表明要終止這個線程,但是,現在線程是阻塞的,它的run方法不能執行,自然也就 * 不能檢查stop,所 以線程不能終止,這個時候,我們就可以用interrupt()方法了:我們在 * thread.stop = true;語句后調用thread.interrupt()方法, 該方法將在線程阻塞時拋出一個中斷 * 信號,該信號將被catch語句捕獲到,一旦捕獲到這個信號,線程就提前終結自己的阻塞狀態,這 * 樣,它就能夠 再次運行run 方法了,然后檢查到stop = true,while循環就不會再被執行,在執 * 行了while后面的清理工作之后,run方法執行完 畢,線程終止。 * */
當代碼調用中須要拋出一個InterruptedException, 你可以選擇把中斷狀態復位, 也可以選擇向外拋出InterruptedException, 由外層的調用者來決定.
不是所有的阻塞方法收到中斷后都可以取消阻塞狀態, 輸入和輸出流類會阻塞等待 I/O 完成,但是它們不拋出 InterruptedException,而且在被中斷的情況下也不會退出阻塞狀態.
嘗試獲取一個內部鎖的操作(進入一個 synchronized 塊)是不能被中斷的,但是 ReentrantLock 支持可中斷的獲取模式即 tryLock(long time, TimeUnit unit)。
轉:https://blog.csdn.net/canot/article/details/51087772