當前位置:
首頁 > 知識 > interrupt方法的使用

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中的源碼如下

interrupt方法的使用


注意紅框中的注釋,調用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線程的步驟

  1. 拋出InterruptedException異常
  2. 複位線程中斷狀態位(設置為false)
  3. 進入catch異常處理代碼塊

經過以上步驟,線程將從wait set中跳出。

interrupt方法的使用

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

Tomcat版本過高出錯問題
PHP 使用 phpmailer 發送電子郵件

TAG:程序員小新人學習 |