稍有java基礎(chǔ)的同學(xué)都知道,在java中創(chuàng)建并啟動一個(gè)線程比較容易,而線程中斷的難度更高一些,并且使用的場景也相對較少。
interrupt()
中斷某一個(gè)線程需要調(diào)用該線程對象的interrupt方法。
public class Demo {
public static void main(String[] args) {
Thread thread = new Thread(new MyTask());
thread.start();
try {
Thread.sleep(1000);
} catch (Exception ex) {
}
thread.interrupt();
}
static class MyTask implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread() + " is running...");
}
}
}
}
運(yùn)行代碼會發(fā)現(xiàn),即使在主線程中執(zhí)行目標(biāo)線程的interrupt()方法,但目標(biāo)線程并沒有停止執(zhí)行。這正是interrupt機(jī)制設(shè)計(jì)的特別之處,當(dāng)主線程發(fā)起目標(biāo)線程中斷的命令后,目標(biāo)線程并不會立即放棄線程的執(zhí)行權(quán)。
中斷標(biāo)志位
java interrupt中斷機(jī)制是當(dāng)主線程向目標(biāo)線程發(fā)起interrupt中斷命令后,目標(biāo)線程的中斷標(biāo)志位被置為true,目標(biāo)線程通過查詢中斷標(biāo)志位自行決定是否停止當(dāng)前線程的執(zhí)行。
這便解釋了上面的代碼中,目標(biāo)線程的中斷標(biāo)志位雖然置為true,但由于并沒有主動采取線程停止的操作,所以線程依然處于Running狀態(tài)。
查詢線程中斷標(biāo)志位的方法有兩種:isInterrupted()和interrupted(),下面分別介紹二者的區(qū)別。
isInterrupted()與interrupted()
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
直接看這兩個(gè)方法的源碼,interrupted()是靜態(tài)方法而isInterrupted()是實(shí)例方法,他們的實(shí)現(xiàn)都是調(diào)用同一個(gè)native方法。主要的區(qū)別是他們的形參ClearInterrupted傳的不一樣。interrupted()在返回中斷標(biāo)志位后會清除標(biāo)志位,isInterrupted()則不清除中斷標(biāo)志位。
接下來改造前面的代碼,實(shí)現(xiàn)線程中斷效果:
public class Demo {
public static void main(String[] args) {
Thread thread = new Thread(new MyTask());
thread.start();
try {
Thread.sleep(100);
} catch (Exception ex) {
}
thread.interrupt();
}
static class MyTask implements Runnable {
@Override
public void run() {
while (true) {
if (Thread.interrupted()) {
break;
}
System.out.println(Thread.currentThread() + " is running...");
}
System.out.println("當(dāng)前中斷標(biāo)志位狀態(tài):" + Thread.currentThread().isInterrupted());
}
}
}

InterruptedException
調(diào)用Thread.sleep()時(shí)都需要捕獲InterruptedException異常。這個(gè)異常的作用是什么?
如果目標(biāo)線程正在執(zhí)行阻塞方法(sleep、join),而其他線程恰好調(diào)用了目標(biāo)線程的interrupt方法試圖中斷目標(biāo)線程,sleep、join這類阻塞方法會檢查線程的中斷標(biāo)志位,并拋出InterruptedException異常。
阻塞方法為何拋出InterruptedException
@Override
public void run() {
while (true) {
if (Thread.interrupted()) {
break;
}
// point 1 : 阻塞方法前邏輯
try {
// point 2 : 阻塞方法中
Thread.sleep(10*000*000);
} catch(InterruptedException ex) {
// 執(zhí)行清除邏輯
}
// point 3 :阻塞方法后邏輯
}
}
前文提到過,interrupt的線程中斷機(jī)制是由發(fā)起線程將目標(biāo)線程的中斷標(biāo)志位置為true,至于是否執(zhí)行線程的中斷由目標(biāo)線程決定。
以上面代碼為例,如果目標(biāo)線程正在執(zhí)行sleep方法而線程阻塞,必須在10000000時(shí)間完成且并執(zhí)行完后續(xù)邏輯,直至循環(huán)里下次interrupted()判斷后才能中斷線程。顯然這不是我們希望看到的,所以阻塞方法會判斷中斷標(biāo)志位,一旦出現(xiàn)中斷的命令就會拋出異常,直接終止阻塞邏輯。
InterruptedException清空中斷標(biāo)志位
拋出InterruptedException異常也會清除中斷標(biāo)志位,如果想要繼續(xù)保留中斷標(biāo)志位的狀態(tài),可以手動觸發(fā)中斷標(biāo)志,代碼如下:
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new RuntimeException(ex);
}
總結(jié)
要想理解java線程中斷的原理,重點(diǎn)就是要掌握中斷標(biāo)志位的使用細(xì)節(jié),其他的邏輯都是圍繞中斷標(biāo)志位設(shè)計(jì)。