博客 / 詳情

返回

✨優雅終止✨Java線程

大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈

前言

Java中的線程,有一個狀態叫做 中斷狀態,用於標記線程是否被中斷過,通過對線程中斷狀態的判斷,可以實現例如 優雅終止線程喚醒線程 等功能。

Thread類中有interrupt()interrupted()和isInterrupted() 方法與線程的中斷有關,本篇文章將對這些方法的具體作用進行詳細解釋。

在文章的最後,還會通過一個典型例子,演示如何通過線程的中斷狀態來優雅的終止線程。

正文

一. interrupt()方法詳解

Thread類提供了interrupt() 方法來中斷線程,哪個線程對象的interrupt() 方法被調用,那麼這個線程就會被中斷。關於interrupt() 方法,有如下注意點。

  1. interrupt() 方法是成員方法
  2. 通常不能在線程內部調用線程的 interrupt() 方法來中斷線程自己
  3. 如果線程正阻塞在 Object#waitObject.wait(long)Object.wait(long, int)Thread#joinThread.join(long)Thread.join(long, int)Thread.sleep(long) Thread.sleep(long, int) 方法上,然後被 interrupt() 方法中斷,此時中斷狀態會被重置為 falsefalse 表示未被中斷),並且被中斷的線程會收到中斷異常 InterruptedException

上面最重要的就是第3點,第3點中羅列出來的方法在使用時都需要try-catch中斷異常InterruptedException,但是在這個中斷異常被拋出前,線程的中斷狀態會被重置為false,這就造成一種現象,因為被中斷而從阻塞方法中喚醒的線程的中斷狀態是false

二. interrupted()方法詳解

Thread類提供了靜態方法interrupted() 來得到調用該方法的線程的中斷狀態。關於interrupted() 方法,有如下注意點。

  1. interrupted() 方法是靜態方法
  2. 哪個線程調用 interrupted() 方法,就會返回這個線程的中斷狀態,然後線程的中斷狀態會重置為 false

interrupted() 方法在調用後,調用線程的中斷狀態會重置為false,這一點請切記。

三. isInterrupted()方法詳解

Thread類提供了isInterrupted() 方法來得到線程的中斷狀態,哪個線程對象的isInterrupted() 方法被調用,就會返回這個線程的中斷狀態。關於isInterrupted() 方法,有如下注意點。

  1. isInterrupted() 方法是成員方法
  2. 哪個線程對象的 isInterrupted() 方法被調用,就會返回這個線程的中斷狀態,並且中斷狀態不會重置為 false

isInterrupted() 方法和interrupted() 方法都能獲取到線程的中斷狀態,不同點在於isInterrupted() 方法獲取到線程的中斷狀態後,線程的中斷狀態不會重置為false,而interrupted() 方法會。

四. 優雅的終止線程

首先明確一點,一個線程的終止,不能由其它線程來強行終止,這也就是為什麼Thread#stopThread#suspend等方法會被廢棄。

優雅終止線程的簡單思路如下。

在線程執行的任務中不斷的判斷一個標誌位,當標誌位滿足某種條件時,任務結束運行,從而線程優雅終止。

如下是一個簡單示例。

public class InterruptedTest {

    @Test
    public void 優雅終止線程的簡單示例() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        // 執行任務
        for (int i = 0; i < 5; i++) {
            threadPool.execute(() -> {
                Thread thread = Thread.currentThread();
                while (!thread.isInterrupted()) {
                    // empty run
                }
                System.out.println(thread.getName() + " 任務執行完畢");
            });
        }

        // 主線程等待2秒,等待任務充分執行
        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);

        // shutdownNow線程池
        threadPool.shutdownNow();

        // 主線程等待1秒,等待任務關閉
        LockSupport.parkNanos(1000 * 1000 * 1000);
    }

}

上述示例中,每個線程執行的任務中會一直判斷當前線程的中斷狀態,如果中斷狀態為true表示線程被中斷了),那麼就退出while循環,從而任務執行完畢,最終線程被優雅終止。

運行測試程序,打印如下。

pool-1-thread-5 任務執行完畢
pool-1-thread-4 任務執行完畢
pool-1-thread-3 任務執行完畢
pool-1-thread-2 任務執行完畢
pool-1-thread-1 任務執行完畢

運行結果表明線程確實被終止了。上述示例中線程執行的任務實際上是 響應中斷 的,因此在線程被調用interrupt() 方法時,能夠響應中斷,從而結束任務的執行,最終線程終止。

再看如下一個例子,是一個看起來響應中斷,卻實際不響應中斷的典型錯誤案例。

public class InterruptedTest {

    @Test
    public void 線程池中的任務不響應中斷的典型案例() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        // 執行任務
        for (int i = 0; i < 5; i++) {
            threadPool.execute(() -> {
                Thread thread = Thread.currentThread();
                while (!thread.isInterrupted()) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        System.out.println(thread.getName() + " 被中斷了");
                    }
                }
                System.out.println(thread.getName() + " 任務執行完畢");
            });
        }

        // 主線程等待2秒,等待任務充分執行
        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);

        // shutdownNow線程池
        threadPool.shutdownNow();

        // 主線程等待1秒,等待任務關閉
        LockSupport.parkNanos(1000 * 1000 * 1000);
    }

}

上述示例中,任務大部分時間是阻塞在Thread.sleep(long) 方法上的,但其實Thread.sleep(long) 方法是響應中斷的,所以上述示例是希望線程被中斷時,線程從Thread.sleep(long) 方法返回,然後判斷線程的中斷狀態,然後結束任務的運行,最終線程終止。

運行測試程序,打印結果如下。

pool-1-thread-1 被中斷了
pool-1-thread-3 被中斷了
pool-1-thread-4 被中斷了
pool-1-thread-2 被中斷了
pool-1-thread-5 被中斷了

可見任務沒有結束運行,故線程也並沒有被終止。原因就是第一節中提到的,線程阻塞在Thread.sleep(long) 方法上時如果被中斷,那麼中斷狀態會被重置為false然後拋出中斷異常InterruptedException,那麼上述示例中,任務在判斷線程的中斷狀態時,中斷狀態會恆為false,故任務永遠不會退出。

改進代碼如下所示。

public class InterruptedTest {

    @Test
    public void 線程池中的任務不響應中斷的典型案例改進() {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        // 執行任務
        for (int i = 0; i < 5; i++) {
            threadPool.execute(() -> {
                Thread thread = Thread.currentThread();
                while (!thread.isInterrupted()) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // 重新將中斷狀態置為true
                        thread.interrupt();
                        System.out.println(thread.getName() + " 被中斷了");
                    }
                }
                System.out.println(thread.getName() + " 任務執行完畢");
            });
        }

        // 主線程等待2秒,等待任務充分執行
        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);

        // shutdownNow線程池
        threadPool.shutdownNow();

        // 主線程等待1秒,等待任務關閉
        LockSupport.parkNanos(1000 * 1000 * 1000);
    }

}

改進思路就是在任務中捕獲到InterruptedException後,再調用一次當前線程對象的interrupt() 方法,來將當前線程的中斷狀態重新置為true,從而任務可以順利結束運行,最終線程終止。

運行測試程序,打印結果如下。

pool-1-thread-5 被中斷了
pool-1-thread-5 任務執行完畢
pool-1-thread-1 被中斷了
pool-1-thread-1 任務執行完畢
pool-1-thread-2 被中斷了
pool-1-thread-2 任務執行完畢
pool-1-thread-3 被中斷了
pool-1-thread-4 被中斷了
pool-1-thread-4 任務執行完畢
pool-1-thread-3 任務執行完畢

可見任務是順利執行完畢,從而線程完成了優雅終止。

總結

關於Java中線程的 中斷 總結如下。

  1. Thread 類提供了 interrupt() 成員方法來中斷線程。哪個線程對象的interrupt() 方法被調用,那麼這個線程被中斷,中斷狀態會被置為true。但是如果線程是阻塞在Object#waitThread#sleep等方法上時被調用interrupt() 方法來中斷,那麼中斷狀態會被重置為false,然後拋出中斷異常InterruptedException
  2. Thread 類提供了 interrupted() 靜態方法來獲取線程的中斷狀態。哪個線程調用interrupted() 方法,就返回這個線程的中斷狀態,同時也 重置這個線程的中斷狀態為false
  3. Thread 類提供了 isInterrupted() 成員方法來獲取線程的中斷狀態。哪個線程對象的isInterrupted() 方法被調用,那麼就返回這個線程的中斷狀態,並且 不會 重置這個線程的中斷狀態為false

線程的 優雅終止 思路如下。

在線程執行的任務中不斷的判斷一個標誌位,當標誌位滿足某種條件時,任務結束運行,從而線程優雅終止。

大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈

user avatar markerhub 頭像 docker_app 頭像 biubiubiu_5ea3ee0e6b5fd 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.