1 目錄
2 概述
Java有3種終止線程的方法:自然結束、調用
stop()
和調用interrupt()
。–《Java多線程編程核心技術》
由於stop()方法並不安全,因此本文將主要講解利用interrupt()中斷線程的方法。
爲了區分,這裏將線程分爲邏輯線程和物理線程:
- 邏輯線程:JAVA管理的抽象線程;
- 物理線程:系統分配的線程。
3 interrupt()
小節簡介:interrupt()方法並不會真正中斷物理線程,只是標記了邏輯線程的中斷標誌。
在main()方法中意圖中斷主線程,然後輸出一條信息檢查線程是否中斷:
public static void main(String[] args) {
Thread.currentThread().interrupt(); // 中斷當前線程(主線程)
log.info("當前線程(主線程)終止後的輸出"); // 該語句正常執行並輸出
}
從代碼邏輯上來講,利用Thread.currentThread().interrupt();
中斷主線程之後,後面的日誌打印語句就不應該執行,但實際上控制檯會正常打印日誌。
也就是說,interrupt()
方法並沒有真正地中斷物理線程。查看該方法的源碼可以發現,官方在註釋中明確指出了,執行中斷操作的interrupt0()
方法只設置線程的中斷標誌,而不是直接中斷線程。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
3.1 中斷標誌
小節簡介:中斷標誌指示了所屬邏輯線程的中斷狀態:true表示邏輯線程中斷。
java.lang.Thread
提供了兩個方法來獲取邏輯線程的中斷標誌:
- interrupted():測試當前線程是否處於中斷狀態並返回中斷標誌;
- isInterrupted():測試線程自身是否處於中斷狀態並返回中斷標誌。
說明:所謂當前線程,指執行當前代碼的線程。例如,在
main()
方法中執行new MyThread().interrupted()
,當前線程就是指main()
所在的線程,即主線程,而不是MyThread線程。
3.1.1 isInterrupted()
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
根據Thread.isInterrupted()
的源碼可知,該方法只能通過Thread對象調用,判斷的是線程自身的中斷狀態。
isInterrupted()
允許傳入一個“清除中斷狀態”的布爾標識:
- true:此次判斷後將線程的中斷標誌置爲false;
- false:默認值。此次判斷後不修改線程的中斷標誌。
3.1.2 interrupted()
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
根據Thread.interrupted()
的源碼可知,該方法是靜態方法,利用currentThread()
獲取到當前線程的Thread對象,然後利用isInterrupted()
判斷該對象的中斷狀態。因此,interrupted()
判斷的是該方法的調用過程所在的線程是否處於中斷狀態。
需要注意的是,Thread.interrupted()
在中斷當前線程時,傳了一個“清除終端狀態”的標識。即,當調用該方法判斷當前線程的中斷狀態之後,會將當前線程的中斷狀態置爲false。如果此後沒有再次中斷當前線程的操作,連續調用該方法時,永遠返回的是false。
public static void main(String[] args) {
log.info("當前線程的狀態:{}" , Thread.interrupted()); // 輸出false
Thread.currentThread().interrupt(); // 終端當前線程
log.info("當前線程的狀態:{}" , Thread.interrupted()); // 輸出true
log.info("當前線程的狀態:{}" , Thread.interrupted()); // 輸出false
log.info("當前線程的狀態:{}" , Thread.interrupted()); // 輸出false
Thread.currentThread().interrupt(); // 再次中斷當前線程
log.info("當前線程的狀態:{}" , Thread.interrupted()); // 輸出true
}
3.1.3 兩者的區別
例如:MyThread繼承自Thread,一直處於執行狀態:
public class MyThread extends Thread {
@Override
public void run() {
while(true) {}
}
}
然後在main()
方法裏啓動MyThread線程並中斷主線程:
public static void main(String[] args) {
MyThread myThread = new MyThread(); // 創建副線程
myThread.start(); // 啓動副線程
Thread.currentThread().interrupt(); // 中斷主線程
log.info("myThread的interrupted:{}", myThread.interrupted());
log.info("myThread的isInterrupted:{}", myThread.isInterrupted());
}
輸出MyThread對象兩種判斷的結果:
2017-12-21 17:19:31 698[main] Main[16] - myThread的interrupted:true
2017-12-21 17:19:31 702[main] Main[17] - myThread的isInterrupted:false
根據上述結果可以明確地理解兩者的區別:myThread.interrupted()
判斷的是主線程的中斷狀態,而myThread.isInterrupted()
判斷的是myThread線程的中斷狀態。
4 中斷線程
注意:對休眠狀態的線程進行中斷操作、對中斷狀態的線程進行休眠操作都會拋出異常。
4.1 異常中斷
小節簡介:interrupt()不會直接中斷活動狀態的物理線程,可以利用它修改的“線程中斷狀態標誌”來使活動狀態的線程拋出異常,結束業務操作。
在run()
方法中通過判斷線程的中斷狀態來結束局部業務操作:
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) break; // 線程中斷狀態標誌爲true時,終止業務
// TODO: 業務邏輯
}
}
}
在main()
方法中利用interrupt()
中斷MyThread的對象,程序最終會自動結束:
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt();
}
但如果while語句後面還有業務邏輯,依舊會執行。這不符合預期(徹底中斷所有業務操作):
public class MyThread extends Thread {
@Override
public void run() {
int i=0;
while (i++<50000) {
if (this.isInterrupted()) break; // 線程中斷狀態標誌爲true時,終止業務
// TODO: 業務邏輯
}
// TODO:業務邏輯
}
}
可以利用拋出和捕獲異常的方式,跳過所有業務邏輯:
public class MyThread extends Thread {
@Override
public void run() {
try{
int i=0;
while (i++<50000) {
if (this.isInterrupted())
throw new RuntimeException("線程已中斷"); // 線程中斷狀態標誌爲true時,拋出異常
// TODO: 業務邏輯
}
// TODO:業務邏輯
} catch(Exception e) {
}
}
}
4.2 return中斷
小節簡介:interrupt()不會直接中斷活動狀態的物理線程,可以利用它修改的“線程中斷狀態標誌”來結束run()方法,以此跳過後面的業務邏輯。
run()方法的返回值類型爲void,判斷線程的中斷狀態後直接返回一個空值,就可以結束run()方法,從而真正的中斷線程:
public class MyThread extends Thread {
private static final Logger log = LoggerFactory.getLogger(MyThread.class);
@Override
public void run() {
int i=0;
while (i++<50000) {
if (this.isInterrupted()) return; // 線程中斷狀態標誌爲true時,結束run()方法
// TODO: 業務邏輯1
}
log.info("業務邏輯2");
}
}