【基礎知識】JAVA線程-線程的中斷

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] - myThreadinterrupted:true
2017-12-21 17:19:31 702[main] Main[17] - myThreadisInterrupted: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");
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章