我們知道線程的start方法,那麼,很自然地會想到停止一個線程使用stop,然而stop方法是“過時的”,“不安全”。stop()方法,直接終止線程,釋放線程所獲的資源,但是在釋放過程中會造成對象狀态不一緻,進而使程式進入未知的境地,已經很久不推薦使用了。是以,Java沒有提供一種安全直接的方法來停止某個線程,但是Java提供了中斷機制。在這裡要着重介紹的是Thread.interrupt() 方法,也就是要分析清楚java的中斷機制。先來看看JDK怎麼描述的:
中斷線程。
- 如果目前線程沒有中斷它自己(這在任何情況下都是允許的),則該線程的 checkAccess 方法就會被調用,這可能抛出 SecurityException。
- 如果線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程中受阻,則其中斷狀态将被清除,它還将收到一個 InterruptedException。
- 如果該線程在可中斷的通道上的 I/O 操作中受阻,則該通道将被關閉,該線程的中斷狀态将被設定并且該線程将收到一個 ClosedByInterruptException。
- 如果該線程在一個 Selector 中受阻,則該線程的中斷狀态将被設定,它将立即從選擇操作傳回,并可能帶有一個非零值,就好像調用了選擇器的 wakeup 方法一樣。
- 如果以前的條件都沒有儲存,則該線程的中斷狀态将被設定。
- 中斷一個不處于活動狀态的線程不需要任何作用。
- 抛出:SecurityException - 如果目前線程無法修改該線程
上述文檔詳細介紹了在各種情況下調用interrupt方法的結果,是以看不明白不要緊,有些知識會在另外的博文裡介紹,這裡我先介紹一些基礎。
1 中斷标志
Java中斷機制是一種協作機制,也就是說通過中斷并不能直接終止另一個線程,而需要被中斷的線程自己進行中斷。這好比是家裡的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎麼注意身體則完全取決于自己。
Java中斷模型也是這麼簡單,每個線程對象裡都有一個boolean類型的辨別(不一定就要是Thread類的字段,實際上也的确不是,這幾個方法最終都是通過native方法來完成的),代表着是否有中斷請求(該請求可以來自所有線程,包括被中斷的線程本身)。例如,當線程t1想中斷線程t2,隻需要線上程t1中将線程t2對象的中斷辨別置為true,然後線程2可以選擇在合适的時候處理該中斷請求,甚至可以不理會該請求,就像這個線程沒有被中斷一樣。
2 interrupt() 不會中斷一個正在運作的線程
在此我先介紹另外兩個方法,這兩個方法有助于我們利用程式分析interrupt():
(1)public static boolean interrupted:注意,它是一個靜态方法,是一個類方法,測試目前線程是否已經中斷。線程的中斷狀态 由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用将傳回 false(在第一次調用已清除了其中斷狀态之後,且第二次調用檢驗完中斷狀态前,目前線程再次中斷的情況除外)。源碼為:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
是以,這是一個命名很不恰當,很有迷惑性的方法,它不僅僅測試了目前線程的是否已經中斷,而且會把中斷狀态清除。
(2)public boolean isInterrupted():注意,這是一個成員方法,是對象的方法。測試線程是否已經中斷。線程的中斷狀态不受該方法的影響。
public class MyThread extends Thread {
private int count = ;
@Override
synchronized public void run() {
super.run();
// 注意這裡,我讓 MyThread 隻列印5次,5次列印結束,這個線程就結束了
for (int i = ; i < ; i++) {
count++;
System.out.printf(this.getName() + " 第 %d 次列印。\n", count);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt();
System.out.println("線程的中斷狀态是:"+myThread.isInterrupted());
}
}
線程的中斷狀态是:true
Thread- 第 次列印。
Thread- 第 次列印。
Thread- 第 次列印。
Thread- 第 次列印。
Thread- 第 次列印。
可見,線程myThread的中斷狀态已經被設定為true,但是它并沒有被停止,好像interrupt()沒有起到任何作用。這也就是上面介紹的,主線程main想讓myThread中斷,但是它沒有理會,依然執行。這也是JDK文檔中提到的第一種情況。
是以,interrupt() 方法隻是将目标線程的中斷狀态設定為true,至于是否對這種中斷進行處理,完全看這個線程本身,也就是我們的代碼是否處理這種情況。上述執行個體代碼我們做一點點修改,增加一個判斷中斷狀态的步驟:
public class MyThread extends Thread {
private int count = ;
@Override
synchronized public void run() {
super.run();
System.out.println("線程已經開始執行了!");
// 注意這裡,我讓 MyThread 隻列印5次,5次列印結束,這個線程就結束了
for (int i = ; i < ; i++) {
if(interrupted()){
System.out.println("線程已經被中斷,我們不往下執行了!");
break;
}
count++;
System.out.printf(this.getName() + " 第 %d 次列印。\n", count);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt();
System.out.println("線程的中斷狀态是:"+myThread.isInterrupted());
}
}
線程的中斷狀态是:true
線程已經開始執行了!
線程已經被中斷,我們不往下執行了!
降到這裡,相信大家對于interrupt()方法已經有了一個基本的了解,那麼,JDK中提到的第二種情況是什麼意思呢?其實很簡單,往下看。
3 InterruptedException
當我想使用Thread.sleep() 方法的時候,IDE就會提醒我要用try/catch包裹:
這是因為sleep方法本身就會去檢查線程的中斷狀态,如果現在的中斷狀态為true,它會抛出InterruptedException。關于這個異常我們來看看JDK文檔:
當線程在活動之前或活動期間處于正在等待、休眠或占用狀态且該線程被中斷時,抛出該異常。有時候,一種方法可能希望測試目前線程是否已被中斷,如果已被中斷,則立即抛出此異常。下列代碼可以達到這種效果:
if (Thread.interrupted()) // Clears interrupted status!
throw new InterruptedException();
這個代碼好眼熟,不就是section 2 中給出的示例代碼嗎?對的,在java中有很多方法是自帶對中斷狀态的判斷的,不用我們像section 2中那樣自己去寫。比如除了sleep(),wait()和join()等也是一樣。還是上面的例子做一點點修改:
public class MyThread1 extends Thread {
@Override
public void run() {
super.run();
System.out.println("線程已經開始執行了!");
// 注意這裡,我讓 MyThread 隻列印5次,5次列印結束,這個線程就結束了
for (int i = ; i < ; i++) {
System.out.printf(this.getName() + " 第 %d 次列印。\n", i+);
try {
System.out.printf("即将開始第 %d 次sleep\n",i+);
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("線程已經被中斷,不能進入sleep!");
}
}
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
myThread.interrupt();
}
}
線程已經開始執行了!
Thread- 第 次列印。
即将開始第 次sleep
線程已經被中斷,不能進入sleep!
java.lang.InterruptedException: sleep interrupted
Thread- 第 at java.lang.Thread.sleep(Native Method)
at easy.MyThread1.run(MyThread1.java:)
次列印。
即将開始第 次sleep
Thread- 第 次列印。
即将開始第 次sleep
Thread- 第 次列印。
即将開始第 次sleep
Thread- 第 次列印。
即将開始第 次sleep
Thread- 第 次列印。
即将開始第 次sleep
也就是:
- main在啟動 myThread 之後,立刻将其中斷狀态設定為true;
- 結果,在第一次調用sleep方法時,該方法去檢查線程的中斷狀态,發現為true,就抛出了InterruptedException異常;
- 然後該方法還将 myThread 的中斷狀态改為false,是以接下來的運作沒有任何問題。
(再次強調一點,這裡的“中斷狀态”隻是為了講解友善的一種形象的說法,真正的原理在native方法中,我們不去深究。)
而文章的前面貼出的JDK文檔的第二種情況是指在sleep、wait、join的狀态下調用interrupt方法的情形,也就是說interrupt方法實際上會觸發這三個方法中的InterruptedException異常機制。
public class MyThread2 extends Thread {
@Override
public void run() {
super.run();
System.out.printf("線程 %s 已經啟動!接下來進入sleep狀态", this.getName());
try {
Thread.sleep(*);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("\n我睡覺被吵醒會咬人的!");
}
}
public static void main(String[] args) {
final MyThread2 myThread = new MyThread2();
myThread.start();
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("\n中斷它!");
myThread.interrupt();
}
}, );// 設定指定的時間time,此處為2000毫秒
}
}
線程 Thread- 已經啟動!接下來進入sleep狀态
中斷它!
java.lang.InterruptedException: sleep interrupted
我睡覺被吵醒會咬人的!
at java.lang.Thread.sleep(Native Method)
at easy.MyThread2.run(MyThread2.java:13)
4 停止一個線程的技巧
section 2 中的代碼其實我故意忽略了一個問題,那就是我用interrupted判斷後結束程序看似已經結束了,其實不然,該代碼隻是結束了for循環:break。如果for循環的底下還有代碼,該代碼會繼續執行:
public class MyThread1 extends Thread {
private int count = ;
@Override
synchronized public void run() {
super.run();
System.out.println("線程已經開始執行了!");
// 注意這裡,我讓 MyThread 隻列印5次,5次列印結束,這個線程就結束了
for (int i = ; i < ; i++) {
if(interrupted()){
System.out.println("線程已經被中斷,我們不往下執行了!");
break;
}
count++;
System.out.printf(this.getName() + " 第 %d 次列印。\n", count);
}
System.out.println("快看,我在for循環的底下");
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
myThread.interrupt();
}
}
線程已經開始執行了!
線程已經被中斷,我們不往下執行了!
快看,我在for循環的底下
解決辦法1:異常法
用try/catch包裹代碼,邏輯代碼放入try中,通過interrupted判斷中斷,一旦發現則主動抛出異常,進而進入了catch塊中,for循環以下的代碼不會執行。
public class MyThread1 extends Thread {
private int count = ;
@Override
synchronized public void run() {
super.run();
System.out.println("線程已經開始執行了!");
// 注意這裡,我讓 MyThread 隻列印5次,5次列印結束,這個線程就結束了
try {
for (int i = ; i < ; i++) {
if(interrupted()){
System.out.println("線程已經被中斷,我們不往下執行了!");
//break;
throw new InterruptedException("異常法"); // 主動抛出異常,throw關鍵字不要忘記
}
count++;
System.out.printf(this.getName() + " 第 %d 次列印。\n", count);
}
System.out.println("快看,我在for循環的底下");
} catch (InterruptedException e) {
System.out.println("進入了catch塊了");
e.printStackTrace();
}
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread1();
myThread.start();
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.interrupt();
}
}
結果:
解決辦法2:return
将break改為return