interrupt方法的使用
從Java的API中可以看出,許多聲明拋出InterruptedException的方法(例如Thread.sleep(long millis)方法)這些方法在拋出InterruptedException之前,Java虛擬機會先將該線程的中斷標識位清除(即設置為false),然後拋出InterruptedException,此時調用isInterrupted()方法將會返回false。
這是什麼意思呢?
以下面的 InterruptJoinTest 類代碼為例說明(詳細代碼見InterruptJoinTest 類代碼)。main線程先啟動了t2線程,然後調用t2.interrupt()方法對t2線程進行中斷,
t2.start();
t2.interrupt();
回顧下JDK中的源碼如下
注意紅框中的注釋,調用t2.interrupt()方法就是將t2的中斷標識位進行了置位(設置為true)。
注意這裡,調用t2.interrupt()方法對t2線程進行中斷也就是對t2的中斷標識位進行了置位,置位後,t2的中斷標識位為true,然後t2線程會拋出InterruptedException異常。再回過頭來看《Java並發編程的藝術》中的話:
在拋出InterruptedException之前,Java虛擬機會先將該線程的中斷標識位清除(即設置為false),然後拋出InterruptedException
也就是說:調用t2.interrupt()方法將中斷標識位設置為true;然後t2線程要拋出InterruptedException異常,t2拋出InterruptedException異常之前Java虛擬機又會將t2的中斷標識位設置為false(即將t2的中斷標識位清除)。那麼意味著線程t2的中斷標識位被設置為true的時間其實相當短暫,馬上隨著InterruptedException異常的拋出又被重置為了false。
以上這些說明,是為了更好的理解下面的代碼示例。
研究InterruptedException類代碼的執行結果,會有更深的體會。
interrupt與join
代碼
public class InterruptJoinTest {
public static void main(String... args) {
T1 t1 = new T1();
T2 t2 = new T2(t1);
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
t2.interrupt();
}
}
class T1 extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "======" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class T2 extends Thread {
private T1 t;
public T2(T1 t) {
this.t = t;
}
public void run() {
try {
t.join();
System.out.println(Thread.currentThread().getName() + "######");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(Thread.currentThread().getName() + "$$$$$$$$$$$$$$$" + Thread.currentThread().isInterrupted());
}
}
}
標黃語句被注釋掉時的列印結果(即不調用t2線程的interrupt方法)
Thread-0======0
Thread-0======1
Thread-0======2
Thread-0======3
Thread-0======4
Thread-1######
標黃語句不被注釋時的列印結果(即調用t2線程的interrupt方法)
Thread-0======0
Thread-1$$$$$$$$$$$$$$$false
Thread-0======1
Thread-0======2
Thread-0======3
Thread-0======4
分析(調用t2線程的interrupt方法的場景下)
t2線程中調用了t1.join(),所以t2線程進入了對象t1的等待隊列(即對象t1的wait set),t2線程要等待t1線程執行完畢才能繼續。此時t2線程在不斷地檢查自身中斷狀態的值(底層來實現)。
主線程即main線程中調用了t2.interrupt(),將t2線程的中斷狀態位設置為true。同時t2線程不斷地檢查自身中斷狀態的值,發現了置位(中斷狀態位被設置為true),所以拋出InterruptedException異常,進行了複位(設置為false),然後t2線程進入了catch異常處理代碼塊。
t1線程沒受什麼影響,繼續自己的運行。
interrupt與wait——如果被interrupt的線程t只是創建了,線程還沒有start,此時調用t.interrupt()方法也沒有將線程t的中斷標識位設置為true——線程start之前interrupt方法不起作用
代碼
import java.util.concurrent.TimeUnit;
public class InterruptWaitTest {
volatile static boolean b = true;
public static void main(String... strings) throws InterruptedException{
final byte[] lock = new byte[0];
Thread t = new Thread(new Runnable() {
public void run() {
while (b) {
}
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " in waiting!!");
lock.wait();
System.out.println("@$%^^$^&*#E%^&*(");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(Thread.currentThread().getName() + " end waiting!!");
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
}, "t");
t.interrupt();
TimeUnit.SECONDS.sleep(1);
System.out.println("########## " + t.isInterrupted());
t.start();
// t.interrupt();
// System.out.println("@@@@@@@@@@@@@ " + t.isInterrupted());
// b = false;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
列印結果
########## false
分析
線程t還沒運行,主線程即main線程中就調用了t.interrupt()方法,此時列印線程t的中斷狀態位,發現線程t的中斷狀態位並未被置位。所以線程t沒有受interrupt()方法的任何影響。啟動線程t後直接執行t裡面的run()方法。
interrupt與wait——如果被interrupt的線程已經start了,在進入wait之前,如果有線程調用了其interrupt方法,那這個wait等於什麼都沒做,會直接跳出來,即從wait set跳出
代碼——線程剛進入wait就檢測到中斷狀態位被置位,所以立即跳出wait set
import java.util.concurrent.TimeUnit;
public class InterruptWaitTest {
volatile static boolean b = true;
public static void main(String... strings) throws InterruptedException{
final byte[] lock = new byte[0];
Thread t = new Thread(new Runnable() {
public void run() {
while (b) {
}
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " in waiting!!");
lock.wait();
System.out.println("@$%^^$^&*#E%^&*(");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(Thread.currentThread().getName() + " end waiting!!");
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
}, "t");
// t.interrupt();
// TimeUnit.SECONDS.sleep(1);
// System.out.println("########## " + t.isInterrupted());
t.start();
t.interrupt();
System.out.println("@@@@@@@@@@@@@ " + t.isInterrupted());
b = false;
TimeUnit.SECONDS.sleep(1);
}
}
運行結果
@@@@@@@@@@@@@ true
t in waiting!!
t end waiting!!
false
分析
線程t在進入對象lock的等待隊列之前,先在主線程即main線程中被調用interrupt()方法。t.interrupt()方法調用完,線程t的中斷標識位已經被設置為true。
因為b為true,線程t在執行while空方法,不會去檢查中斷狀態。
b變為false,線程t終於執行到lock.wait(),線程t進入了lock的等待隊列即wait set。線程t不斷循環檢查自己的中斷標識位,發現中斷標識位已經為true,馬上會拋出 InterruptedException,進入catch異常處理代碼塊。
interrupt與wait總結
線程t如果在啟動前調用t.interrupt()方法是不起作用的。
線程t啟動後,調用t.interrupt()方法,在線程t進入某個對象的wait set後線程t會不斷檢查自己的中斷標識位狀態,若發現中斷標識位狀態已被設置為true,線程t就會拋出InterruptedException異常。
注意,只有線程t進入某個對象的wait set,線程t才會開始不斷循環檢查自身的中斷標識位狀態。否則即使線程t的中斷標識位被設置為true,線程t不檢查中斷標識位,也不會拋出InterruptedException異常。
interrupt與sleep——《java並發編程的藝術》示例代碼
代碼
import java.util.concurrent.TimeUnit;
public class InterruptSleepTest {
public static void main(String[] args) throws Exception {
// sleepThread不停的嘗試睡眠
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
sleepThread.setDaemon(true);
// busyThread不停的運行
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠5秒,讓sleepThread和busyThread充分運行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
// 防止sleepThread和busyThread立刻退出
TimeUnit.SECONDS.sleep(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
while (true) {
// SleepUtils.second(10);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("!@#$%^&*(!@#$%^&*(!@#$%^&*");
}
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
}
列印結果
!@#$%^&*(!@#$%^&*(!@#$%^&*
SleepThread interrupted is false
BusyThread interrupted is true
分析
sleepThread.interrupt();
SleepRunner進行中斷,因為SleepRunner正在進行sleep,方法內部會不斷檢查中斷狀態的值,檢測到中斷狀態已經被置位(設置為true),那麼就會拋出InterruptedException異常,並且將中斷狀態複位。所以看到的結果是跳進了catch異常處理代碼塊,且因為被複位,所以中斷狀態位是false。
busyThread.interrupt();
BusyRunner進行中斷,因為BusyRunner沒有sleep、wait、join,不會去檢查中斷狀態,所以線程A不會拋出 InterruptedException,而會一直執行著自己的操作。這樣interrupt()置位一直沒有清除,所以中斷狀態位還是true。
程序執行時,最後停頓2秒後終止,為什麼BusyRunner對應的線程沒有一直執行呢?不是while(true)嗎?因為 BusyRunner 是Daemon線程。main線程執行完畢後,JVM中就沒有非Daemon線程了,所以JVM就退出了。線程 BusyRunner 就跟著一起結束了。
★小結--interrupt的應用——喚醒阻塞線程
喚醒的條件
線程t處於以下三種情況下,
線程t因為調用某對象的wait()方法進入某對象的wait set;
線程t因為調用其他線程的join()進入其他線程(對象)的wait set;
線程t因為調用t.sleep()自身進入超時等待狀態。
實際上這三種情況下線程t都處於wait set。
線程t在這三種狀態下會不斷循環檢查自身的中斷標識位,此時如果有線程調用t.interrupt()方法,會將線程t的中斷標識位設置為true,那麼線程t將接收到一個中斷異常(InterruptedException),然後線程t的中斷標識位會被複位為false,再然後線程t會進入catch異常處理代碼塊。
套路——喚醒處於wait set線程的步驟
- 拋出InterruptedException異常
- 複位線程中斷狀態位(設置為false)
- 進入catch異常處理代碼塊
經過以上步驟,線程將從wait set中跳出。


※Tomcat版本過高出錯問題
※PHP 使用 phpmailer 發送電子郵件
TAG:程序員小新人學習 |