java.lang.Thread類有一個 interrupt 方法,該方法直接對執行緒呼叫。當被interrupt的執行緒正在sleep或wait時,會丟擲 InterruptedException 異常。事實上, interrupt 方法只是改變目標執行緒的中斷狀態(interrupt status),而那些會丟擲InterruptedException 異常的方法,如wait、sleep、join等,都是在方法內部不斷地檢查中斷狀態的值,如果發現中斷,則丟擲InterruptedException異常。
interrupt方法
Thread例項方法:必須由其它執行緒獲取被呼叫執行緒的例項後,進行呼叫。實際上,只是改變了被呼叫執行緒的內部中斷狀態;
Thread.interrupted方法
Thread類方法:必須在當前執行執行緒內呼叫,該方法返回當前執行緒的內部中斷狀態,然後清除中斷狀態(置為false) ;
isInterrupted方法
Thread例項方法:用來檢查指定執行緒的中斷狀態。當執行緒為中斷狀態時,會返回true;否則返回false。
上面的一些說法比較抽象,為了驗證上述說法,寫幾個demo來驗證一下。
一、中斷和中斷檢查
1、interrupt方法可能不會中斷執行緒
首先得明確第一個問題:interrupt方法是用於中斷執行緒的方法,但是實際如果執行緒內沒有sleep等阻塞方法,它實際上並不會中斷執行緒,就算有sleep等方法執行,但是如果將異常捕獲了,那它也不會中斷執行緒的執行。看以下程式碼:
public class ThreadTest1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task("mytask"));
t.start();
t.interrupt();
}
static class Task implements Runnable {
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
int i = 0;
while (true) {
System.out.println(i++);
}
}
}
}
執行該程式,將會進入死迴圈,不斷列印i的自增值,最後整型溢位也不會停止,主執行緒呼叫的interrupt方法根本無法阻止執行緒繼續執行。
正是之前所說的,“interrupt方法只是改變了被呼叫執行緒的內部中斷狀態“,那如何檢查執行緒的中斷狀態呢?
2、isInterrupted例項方法檢查中斷狀態
接下來我們呼叫Thread類的isInterrupted例項方法來檢查執行緒的中斷狀態
public class ThreadTest2 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task("mytask"));
t.start();
t.interrupt();
}
static class Task implements Runnable {
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
//檢查兩次中斷狀態,都是true
System.out.println("first:"+Thread.currentThread().isInterrupted());
System.out.println("second:"+Thread.currentThread().isInterrupted());
System.out.println("task " + name + " is over");
}
}
}
上述程式碼的執行結果如下
first:true
second:true
task mytask is over
主執行緒呼叫了中斷方法,執行緒內呼叫執行緒的isInterrupted方法,輸出都是true,表示都檢測到了中斷。為什麼要輸出兩次一模一樣的檢測結果呢?是為了驗證第一次呼叫的isInterrupted方法並沒有改變中斷狀態。
3、interrupted靜態方法檢查中斷狀態
interrupted方法是Thread類的靜態方法,它也能檢查當前執行緒的中斷狀態,但是隻能檢查一次:這個靜態方法有個天坑,它返回中斷狀態之後,會將中斷標誌復位成false,所以第二次呼叫該靜態方法就會發現中斷標誌被改變了。
public class ThreadTest3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task("mytask"));
t.start();
t.interrupt();
}
static class Task implements Runnable {
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
//第一次呼叫返回中斷狀態,並將中斷狀態復位
System.out.println("first :" + Thread.interrupted());
//第二次呼叫返回復位後的中斷狀態
System.out.println("second:" + Thread.interrupted());
System.out.println("task " + name + " is over");
}
}
}
中斷程式碼的輸出結果為
first :true
second:false
task mytask is over
可以看到,重複呼叫Thread.interrupted()方法,得到的結果並不一樣,原因就是第一次呼叫的時候中斷狀態被複位了。
二、中斷拋異常的情況討論
1、阻塞中斷並丟擲異常
既然是中斷方法,那它肯定能在某些情況下中斷執行緒的執行,什麼情況下呢?就是在大多數阻塞方法下,比如執行緒正在sleep、wait、join等。
看下以下程式碼示例
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task("mytask"));
t.start();
t.interrupt();
}
static class Task implements Runnable {
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("thread has been interrupt!");
}
// 阻塞情況下中斷,丟擲異常後執行緒恢復非中斷狀態,即 interrupted = false
System.out.println("isInterrupted: " +
Thread.currentThread().isInterrupted());
System.out.println("task " + name + " is over");
}
}
}
它的執行結果如下
thread has been interrupt!
isInterrupted: false
task mytask is over
執行緒在sleep期間被中斷並丟擲了InterruptedException異常,丟擲異常後立即重置了中斷狀態,所以接下來的檢查中斷方法得到的結果是false。
2、interrupted不會中斷鎖阻塞
對於sleep等阻塞方法,遇到interrupt中斷方法會丟擲異常,但是對於鎖阻塞,則不會,看以下案例
public class ThreadTest4 {
public static void main(String[] args) throws InterruptedException {
Task mytask = new Task("mytask");
Thread t1 = new Thread(mytask);
Thread t2 = new Thread(mytask);
t1.start();
t2.start();
t2.interrupt();
}
static class Task implements Runnable {
String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
synchronized (this) {
System.out.println(new Date() + ":" + Thread.currentThread().getName() + ": start run");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
System.out.println(new Date() + ":" + Thread.currentThread().getName() + ":catch interrupted exception");
}
}
System.out.println(new Date() + ":" + Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted());
}
}
}
該程式執行結果如下
Fri Jun 14 15:11:52 CST 2024:Thread-0: start run
Fri Jun 14 15:11:55 CST 2024:Thread-1: start run
Fri Jun 14 15:11:55 CST 2024:Thread-0:false
Fri Jun 14 15:11:55 CST 2024:Thread-1:catch interrupted exception
Fri Jun 14 15:11:55 CST 2024:Thread-1:false
執行緒0持有鎖之後等待了3秒鐘,在等待期間,執行緒1嘗試進入方法區,但是拿不到鎖,所以進不去,進入鎖等待狀態,這時候主執行緒呼叫了執行緒1的執行緒中斷方法interrupt,但是執行緒1並沒有任何反映,等待執行緒0釋放了鎖之後,拿到鎖,這時候它開始執行sleep方法,由於執行緒中斷,丟擲了InterruptedException,所以它“沒睡”,直接列印資訊後結束了執行緒。
如果我們不想執行緒1丟擲異常,該怎麼做呢?其實只需要稍微修改一點程式碼:
System.out.println(new Date() + ":" + Thread.currentThread().getName() + ": start run");
將上面這行程式碼改成下面這樣子
System.out.println(new Date() + ":" + Thread.currentThread().getName() +":" + Thread.interrupted() + ": start run");
只是進入方法區之後加了一點點邏輯:Thread.interrupted()
再次執行上面的主方法,得到的結果為
Fri Jun 14 15:20:09 CST 2024:Thread-0:false: start run
Fri Jun 14 15:20:12 CST 2024:Thread-1:true: start run
Fri Jun 14 15:20:12 CST 2024:Thread-0:false
Fri Jun 14 15:20:15 CST 2024:Thread-1:false
現成1不拋異常了,而且正常等待了3秒鐘。。。可見,Thread.interrupted方法真是個隱藏的bug方法啊
三、總結
1、呼叫interrupt方法中斷執行緒實際上只是設定了中斷標誌,只有執行緒在執行sleep、wait等阻塞的方法的時候才會丟擲中斷異常,但是並不會中斷鎖阻塞;中斷丟擲異常後會重置中斷狀態為false
2、可以呼叫Thread類的isInterrupted例項方法檢測執行緒的中斷狀態,該方法不會重置中斷狀態為false
3、可以呼叫Thread類的interrupted靜態方法檢測執行緒的中斷狀態,該方法會重置中斷狀態為false,所以要慎用。
最後都看到這裡了,歡迎光臨我的個人部落格:https://blog.kdyzm.cn ~