一、中斷
線程的幾種狀態:新建、就緒、運行、阻塞、死亡。參考:線程的幾種狀態轉換
線程的可運行狀態並不代表線程一定在運行(runnable != running ) 。 大家都知道:所有現代桌面和服務器操作系統都使用了搶占式的線程調度策略 。一旦線程開始執行,並不是總是保持持續運行狀態的。當系統分給它的時間片(非常小的運行時間單位)用完以后,不管程序有沒有執行完,線程被強制放棄CPU,進入就緒狀態,直到下次被調度后開始繼續執行。也就是說, Runnable可運行狀態的線程處於兩種可能的情況下:(1)占用CPU運行中,(2)等待調度的就緒狀態。 這里要聲明一下:處於等待調度的就緒狀態線程和處於阻塞的線程是完全不同的。就緒的線程是因為時間片用完而放棄CPU,其隨時都有可能再次獲得CPU而運行,這一切取決於分時OS的線程調度策略。
在很多操作系統的專業術語中,這種因時間片用完而被剝奪CPU的情況我們叫做線程中斷 。注意這和我們下面要將得中斷線程是兩個完全不同的概念。事實上,我們不可能通過應用程序來控制CPU的線程中斷,除非我們能夠自由調用OS的內核。
中斷可以理解為線程的一個標識位屬性,表示一個運行中的線程是否被其他線程進行了中斷操作。中斷好比其他線程對該線程打了一個招呼,其他線程通過調用該線程的interrupt()方法對其進行中斷操作。
一個正在運行的線程除了正常的時間片中斷之外,能否被其他線程控制?或者說其他線程能否讓指定線程放棄CPU或者提前結束運行? 除了線程同步機制之外,還有兩種方法:
(1) stop(), suspend(), resume() 和Runtime.runFinalizersOnExit() ,但這些方法已經被廢棄。
(2) interrupt() 方法
例:創建了一個線程countThread,它不斷地進行變量累加,而主線程嘗試對其進行中斷操作和停止操作。
import java.util.concurrent.TimeUnit; public class Shutdown { public static void main(String[] args) throws Exception { Runner one = new Runner(); Thread countThread = new Thread(one, "CountThread"); countThread.start(); // 睡眠1秒,main線程對CountThread進行中斷,使CountThread能夠感知中斷而結束 TimeUnit.SECONDS.sleep(1); //Thread.sleep(1000); countThread.interrupt(); System.out.println("是否停止1:" + countThread.isInterrupted()); Runner two = new Runner(); countThread = new Thread(two, "CountThread"); countThread.start(); // 睡眠1秒,main線程對Runner two進行取消,使CountThread能夠感知on為false而結束 TimeUnit.SECONDS.sleep(1); two.cancel(); System.out.println("是否停止2:" + countThread.isInterrupted()); } private static class Runner implements Runnable { private long i; private volatile boolean on = true; @Override public void run() { while (on && !Thread.currentThread().isInterrupted()) { i++; } System.out.println("Count i = " + i); } public void cancel() { on = false; } } }
運行結果:
是否停止1:true Count i = 574867187 是否停止2:false Count i = 581254533
main線程通過中斷操作和cancel()方法均可使線程countThread終止。
當我們調用countThread.interrput()的時候,線程countThread的中斷狀態(interrupted status) 會被置位。我們可以通過Thread.currentThread().isInterrupted() 來檢查這個布爾型的中斷狀態。
在Core Java中有這樣一句話:"沒有任何語言方面的需求要求一個被中斷的程序應該終止。中斷一個線程只是為了引起該線程的注意,被中斷線程可以決定如何應對中斷 "。
while循環有一個決定因素就是需要不停的檢查自己的中斷狀態。當外部線程調用該線程的interrupt 時,使得中斷狀態置位。這時該線程將終止循環,不再執行循環中的內容。
這說明: interrupt中斷的是線程的某一部分業務邏輯,前提是線程需要檢查自己的中斷狀態(isInterrupted())。
參考:
《Java並發編程的藝術》
另:Thread.sleep和TimeUnit.SECONDS.sleep的區別與聯系
二、interrupt()、interrupted() 和 isInterrupted()方法的區別
1、interrupt()方法
interrupt()方法用於中斷線程。調用該方法的線程的狀態為將被置為"中斷"狀態。
中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作。中斷好比其他線程對該線程打了個招呼,其他線程通過調用該線程的interrupt()方法對其進行中斷操作。
注意:線程中斷僅僅是置線程的中斷狀態位,不會停止線程。需要用戶自己去監視線程的狀態為並做處理。
2、interrupted() 和 isInterrupted()
public static boolean interrupted() { return currentThread().isInterrupted(true); } public boolean isInterrupted() { return isInterrupted(false); } private native boolean isInterrupted(boolean ClearInterrupted);
從源代碼可以看出:
interrupted()測試的是當前線程(current thread)的中斷狀態,且這個方法會清除中斷狀態。是靜態方法(它測試的是當前線程的中斷狀態)
isInterrupted()測試的是調用該方法的對象所表示的線程,且這個方法不會清除中斷狀態。是實例方法(它測試的是實例對象所表示的線程的中斷狀態)
關於方法isInterrupted( boolean ClearInterrupted):
通過參數名ClearInterrupted可以知道,這個參數代表是否要清除狀態位。
如果這個參數為true,說明返回線程的狀態位后,要清掉原來的狀態位(恢復成原來情況)。這個參數為false,就是直接返回線程的狀態位。
這兩個方法很好區分,只有當前線程才能清除自己的中斷位(對應interrupted()方法)
例:1. interrupted()方法
1 public class InterruptedTest { 2 public static void main(String[] args) throws InterruptedException { 3 MyThread thread = new MyThread(); 4 thread.start(); 5 Thread.sleep(1000); 6 7 System.out.println("當前正在執行的線程:" + Thread.currentThread().getName()); 8 9 thread.interrupt(); // Thread.currentThread().interrupt(); 10 11 System.out.println("是否停止?=" + thread.interrupted());// false,main線程沒有被中斷 12 } 13 } 14 15 class MyThread extends Thread { 16 @Override 17 public void run() { 18 int i = 0; 19 super.run(); 20 for (; i < 500000; i++) { 21 i++; 22 } 23 System.out.println("i: " + i); 24 } 25 }
運行結果:
i: 500000 當前正在執行的線程:main 是否停止?=false
第4行啟動thread線程,第5行使main線程睡眠1秒鍾從而使得thread線程有機會獲得CPU執行。
main線程睡眠1s鍾后,恢復執行到第7行,請求中斷 thread線程。
第11行測試線程是否處於中斷狀態,這里測試的是哪個線程呢?答案是main線程。因為:
(1)interrupted()測試的是當前的線程的中斷狀態
(2)main線程執行了第11行語句,故main線程是當前線程
如果將第9行換成Thread.currentThread().interrupt();,則運行結果為:
i: 500000 當前正在執行的線程:main 是否停止?=true
2. isInterrupted()方法
1 public class InterruptedTest { 2 public static void main(String[] args) throws InterruptedException { 3 MyThread thread = new MyThread(); 4 thread.start(); 5 //Thread.sleep(1000); 6 7 System.out.println("當前正在執行的線程:" + Thread.currentThread().getName()); 8 9 thread.interrupt(); 10 11 System.out.println("是否停止?=" + thread.isInterrupted());// false,main線程沒有被中斷 12 } 13 } 14 15 class MyThread extends Thread { 16 @Override 17 public void run() { 18 int i = 0; 19 super.run(); 20 for (; i < 500000; i++) { 21 i++; 22 } 23 System.out.println("i: " + i); 24 } 25 }
運行結果:
當前正在執行的線程:main 是否停止?=true i: 500000
在第11行,是thread對象調用的isInterrupted()方法。因此,測試的是thread對象所代表的線程的中斷狀態。由於在第9行,main線程請求中斷 thread線程,故在第11行的結果為: true
另外:當線程被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時。調用它的interrput()方法。可想而知,沒有占用CPU運行的線程是不可能給自己的中斷狀態置位的。這就會產生一個InterruptedException異常。例:
public class InterruptedSleep { public static void main(String[] args) { Runnable tr = new TestRunnable(); Thread th1 = new Thread(tr); th1.start(); // 開始執行分線程 while (true) { th1.interrupt(); // 中斷這個分線程 } } } class TestRunnable implements Runnable { public void run() { try { Thread.sleep(1000000); // 這個線程將被阻塞1000秒 } catch (InterruptedException e) { e.printStackTrace(); System.out.println("是否停止:" + Thread.currentThread().isInterrupted()); } } }
運行結果:
java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at threadArt.TestRunnable.run(InterruptedSleep.java:17) at java.lang.Thread.run(Thread.java:745) 是否停止:true
因此:
要中斷一個Java線程,可調用線程類(Thread)對象的實例方法:interrupte();然而interrupte()方法並不會立即執行中斷操作;具體而言,這個方法只會給線程設置一個為true的中斷標志(中斷標志只是一個布爾類型的變量),而設置之后,則根據線程當前的狀態進行不同的后續操作。
1. 如果線程的當前狀態處於非阻塞狀態,那么僅僅是線程的中斷標志被修改為true而已;
2. 如果線程的當前狀態處於阻塞狀態,那么在將中斷標志設置為true后,還會有如下三種情況之一的操作:
(1) 如果是wait、sleep以及jion三個方法引起的阻塞,那么會將線程的中斷標志重新設置為false,並拋出一個InterruptedException;
(2) 如果是java.nio.channels.InterruptibleChannel進行的io操作引起的阻塞,則會對線程拋出一個ClosedByInterruptedException;(待驗證)
(3) 如果是輪詢(java.nio.channels.Selectors)引起的線程阻塞,則立即返回,不會拋出異常。(待驗證)
如果在中斷時,線程正處於非阻塞狀態,則將中斷標志修改為true,而在此基礎上,一旦進入阻塞狀態,則按照阻塞狀態的情況來進行處理;例如,一個線程在運行狀態中,其中斷標志被設置為true,則此后,一旦線程調用了wait、jion、sleep方法中的一種,立馬拋出一個InterruptedException,且中斷標志被清除,重新設置為false。
通過上面的分析,我們可以總結,調用線程類的interrupted方法,其本質只是設置該線程的中斷標志,將中斷標志設置為true,並根據線程狀態決定是否拋出異常。因此,通過interrupted方法真正實現線程的中斷原理是:開發人員根據中斷標志的具體值,來決定如何退出線程。
參考:
JAVA多線程之中斷機制(stop()、interrupted()、isInterrupted())
使用interrupte中斷線程的真正用途