成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java并發編程:如何正確停止線程

開發 前端
你可以使用??interrupt??方法來通知線程應該中斷執行,而被中斷的線程擁有決定權,即它不僅可以決定何時響應中斷并停止,還可以選擇忽略中斷。

1. 什么時候需要停止線程?

通常情況下,線程在創建并啟動后,會自然運行到結束。但在某些情況下,我們可能需要在運行過程中停止線程,比如:

  • 用戶主動取消執行;
  • 線程在運行時發生錯誤或超時,需要停止;
  • 服務需要立即關閉。

這些情況都需要我們主動停止線程。然而,安全且可靠地停止線程并不容易。Java 語言并沒有提供一種機制來確保線程能夠立即且正確地停止,但它提供了interrupt方法,這是一種協作機制。

2. 如何正確停止線程?

你可以使用interrupt方法來通知線程應該中斷執行,而被中斷的線程擁有決定權,即它不僅可以決定何時響應中斷并停止,還可以選擇忽略中斷。

換句話說,如果被停止的線程不想被中斷,那么我們除了讓它繼續運行或強制關閉進程外,別無他法。

3. 為什么不強制停止?而是通知、協作

事實上,大多數時候我們想要停止線程時,至少會讓它運行到結束。比如,即使我們在關閉電腦時,也會進行很多收尾工作,結束一些進程并保存一些狀態。

線程也是如此。我們想要中斷的線程可能并不是由我們啟動的,我們對其執行的業務邏輯并不熟悉。如果我們希望它停止,實際上是希望它在停止前完成一系列的保存和交接工作,而不是立即停止。

舉個生活中的例子:

某天下午你得知公司要裁員,覺得自己很可能在名單內,便開始找新工作。幾周后,成功拿到另一家公司 offer。你準備搬到新公司附近,可家里東西多,只能分批處理。搬到一半時,發現公司裁員結束,自己不在名單中。

你十分高興,因為喜歡這家公司,決定留下。但一半物品已搬到新家,還得搬回來。

試想,若此時你決定立刻停止搬家、什么都不做,已搬走的物品就會丟失,這無疑是場災難!

生活中還有很多類似的例子,比如從電腦剪切文件到 U 盤。如果剪切到一半時停止,需要恢復到原來的狀態,不能一半文件在 U 盤,一半在電腦上。

4. 代碼實踐

4.1. 錯誤的線程停止方式

使用stop()方法終止線程執行會導致線程立即停止,這可能會引發意外問題。

public class StopThread implements Runnable {
    @Override
    public void run() {
        System.out.println("Start moving...");
        for (int i = 1; i <= 5; i++) {
            // 模擬搬家所需時間
            int j = 50000;
            while (j > 0) {
                j--;
            }
            System.out.println(i + " batches have been moved");
        }
        System.out.println("End of moving");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        // 稍后嘗試停止
        Thread.sleep(2);
        thread.stop();
    }
}

輸出結果(結果可能因計算機性能不同而有所差異,你可以調整時間以獲得相同的輸出):

Start moving...
1 batches have been moved
2 batches have been moved
3 batches have been moved

可以看到,stop強制線程結束,導致只搬了三批物品,結束后也沒有搬回來!

出于安全考慮,stop方法已被官方棄用。你可以在源碼中看到它被標記為過時。

@Deprecated
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
}

4.2. 直接使用interrupt方法,線程并未停止

在主線程中使用interrupt方法中斷目標線程,但目標線程并未感知到中斷標志,即它不打算處理中斷信號。

public class InterruptThreadWithoutFlag implements Runnable {
    @Override
    public void run() {
        System.out.println("Start moving...");
        for (int i = 1; i <= 5; i++) {
            // 模擬搬家所需時間
            int j = 50000;
            while (j > 0) {
                j--;
            }
            System.out.println(i + " batches have been moved");
        }
        System.out.println("End of moving");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        // 稍后
        Thread.sleep(2);
        thread.interrupt();
    }
}

輸出:

Start moving...
1 batches have been moved
2 batches have been moved
3 batches have been moved
4 batches have been moved
5 batches have been moved
End of moving

你會發現沒有任何效果。我們使用interrupt中斷了這個線程,但它似乎完全忽略了我們的中斷信號。就像前面提到的,線程是否停止取決于它自己,因此我們需要修改線程的邏輯,使其能夠響應中斷,從而停止線程。

4.3. 使用interrupt時,線程識別中斷標志

當指定線程被中斷時,在線程內部調用Thread.currentThread().isInterrupted()會返回true,可以根據此進行中斷后的處理邏輯。

public class InterruptThread implements Runnable {
    @Override
    public void run() {
        System.out.println("Start moving...");
        for (int i = 1; i <= 5; i++) {
            if (Thread.currentThread().isInterrupted()) {
                // 做一些收尾工作
                break;
            }
            // 模擬搬家所需時間
            int j = 50000;
            while (j > 0) {
                j--;
            }
            System.out.println(i + " batches have been moved");
        }
        System.out.println("End of moving");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptThread());
        thread.start();
        Thread.sleep(2);
        thread.interrupt();
    }
}

輸出(結果可能不一致):

Start moving...
1 batches have been moved
End of moving

從輸出結果來看,它與使用stop方法的結果類似,顯然線程在執行完之前被停止了,interrupt()方法的中斷是有效的,這是一種標準的處理方式。

4.4. 中斷某個線程時,線程正在睡眠

如果線程處理中使用了sleep方法,在sleep期間的中斷也可以響應,而無需檢查中斷標志。

例如,使用Thread.sleep(1)模擬每次搬家所需的時間。在主線程中,等待 3ms 后中斷,因此預計在搬完 2 到 3 批物品后會被中斷。代碼如下:

public class InterruptWithSleep implements Runnable {
    @Override
    public void run() {
        System.out.println("Start moving...");
        for (int i = 1; i <= 5; i++) {
            // 模擬搬家所需時間
            try {
                Thread.sleep(1);
                System.out.println(i + " batches have been moved");
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
                break;
            }
        }
        System.out.println("End of moving");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptWithSleep());
        thread.start();
        // 稍后
        Thread.sleep(3);
        thread.interrupt();
    }
}

輸出:

Start moving...
1 batches have been moved
2 batches have been moved
sleep interrupted
End of moving

發現了嗎?額外輸出了sleep interrupted。這是因為發生了中斷異常,我們在catch到InterruptedException后輸出了e.getMessage()。

為什么會拋出異常?

這是因為當線程處于sleep狀態時,如果接收到中斷信號,線程會響應這個中斷,而響應中斷的方式非常特殊,就是拋出java.lang.InterruptedException: sleep interrupted異常。

因此,當我們的程序中有sleep方法的邏輯,或者可以阻塞線程的方法(如wait、join等),并且可能會被中斷時,我們需要注意處理InterruptedException異常。我們可以將其放在catch中,這樣當線程進入阻塞過程時,仍然可以響應中斷并進行處理。

4.5. 當sleep方法與isInterrupted結合使用時會發生什么?

你注意到在示例 3 的代碼中,我們在捕獲異常后使用了break來主動結束循環嗎?那么,我們是否可以在catch中不使用break,而是在循環入口處判斷isInterrupted是否為true呢?

讓我們試試:

public class SleepWithIsInterrupted implements Runnable {
    @Override
    public void run() {
        System.out.println("Start moving...");
        for (int i = 1; i <= 5; i++) {
            if (Thread.currentThread().isInterrupted()) {
                // 做一些收尾工作
                break;
            }
            // 模擬搬家所需時間
            try {
                Thread.sleep(1);
                System.out.println(i + " batches have been moved");
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }
        System.out.println("End of moving");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepWithIsInterrupted());
        thread.start();
        // 稍后
        Thread.sleep(3);
        thread.interrupt();
    }
}

輸出(你可能需要調整主線程執行Thread.sleep的時間以獲得相同的輸出):

Start moving...
1 batches have been moved
2 batches have been moved
sleep interrupted
4 batches have been moved
5 batches have been moved
End of moving

為什么在輸出sleep interrupted后,它繼續搬了第四和第五批物品?

原因是,一旦sleep()響應了中斷,它會重置isInterrupted()方法中的標志,因此在上面的代碼中,循環條件檢查時,Thread.currentThread().isInterrupted()的結果始終為false,導致程序無法退出。

一般來說,在實際的業務代碼中,主邏輯更為復雜,因此不建議在這里直接使用try-catch處理中斷異常,而是直接將異常向上拋出,由調用方處理。

可以將當前邏輯封裝到一個單獨的方法中,并將中斷后的收尾處理也封裝到另一個方法中,如下所示:

public class SleepSplitCase implements Runnable {
    @Override
    public void run() {
        try {
            move();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
            goBack();
        }
    }

    private void move() throws InterruptedException {
        System.out.println("Start moving...");
        for (int i = 1; i <= 5; i++) {
            // 模擬搬家所需時間
            Thread.sleep(1);
            System.out.println(i + " batches have been moved");
        }
        System.out.println("End of moving");
    }

    private void goBack() {
        // 做一些收尾工作
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepSplitCase());
        thread.start();
        // 稍后
        Thread.sleep(3);
        thread.interrupt();
    }
}

4.6. 重新中斷

有沒有辦法在catch之外處理goBack方法?

如前所述,當中斷發生并拋出InterruptedException時,isInterrupted的結果會被重置為false。但是,支持再次調用interrupt,這會使isInterrupted的結果變為true。

基于這個前提,我們可以在示例 5 的實現中將run方法改為以下形式:

@Override
public void run() {
    try {
        move();
    } catch (InterruptedException e) {
        System.out.println(e.getMessage());
        Thread.currentThread().interrupt();
    }
    if (Thread.currentThread().isInterrupted()) {
        goBack();
    }
}

這樣可以避免在catch代碼塊中處理業務邏輯!

4.7 判斷中斷是否發生的方法

  • boolean isInterrupted(): 判斷當前線程是否被中斷;
  • static boolean interrupted(): 判斷當前線程是否被中斷,但在調用后會將中斷標志直接設置為false,即清除中斷標志。

注意,interrupted()方法的目標是當前線程,無論該方法是從哪個實例對象調用的,從源碼中可以很容易看出:

public class CheckInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread subThread = new Thread(() -> {
            // 無限循環
            for (; ; ) {
            }
        });

        subThread.start();
        subThread.interrupt();
        // 獲取中斷標志
        System.out.println("isInterrupted: " + subThread.isInterrupted());
        // 獲取中斷標志并重置
        // (盡管 interrupted() 是由 subThread 線程調用的,但實際執行的是當前線程。)
        System.out.println("isInterrupted: " + subThread.interrupted());

        // 中斷當前線程
        Thread.currentThread().interrupt();
        System.out.println("isInterrupted: " + subThread.interrupted());
        // Thread.interrupted() 與 subThread.interrupted() 效果相同
        System.out.println("isInterrupted: " + Thread.interrupted());
    }
}

輸出:

isInterrupted: true
isInterrupted: false
isInterrupted: true
isInterrupted: false

interrupted()會重置中斷標志,因此最后的輸出結果變為false。

5. JDK 內置的可以響應中斷的方法

主要有以下方法可以響應中斷并拋出InterruptedException:

  1. Object.wait()/wait(long)/wait(long, int)
  2. Thread.sleep(long)/sleep(long, int)
  3. Thread.join()/join(long)/join(long, int)
  4. java.util.concurrent.BlockingQueue.take()/put(E)
  5. java.util.concurrent.locks.Lock.lockInterruptibly()
  6. java.util.concurrent.CountDownLatch.await
  7. java.util.concurrent.CyclicBarrier.await
  8. java.util.concurrent.Exchanger.exchange(V)
  9. java.nio.channels.InterruptibleChannel的相關方法
  10. java.nio.channels.Selector的相關方法
責任編輯:武曉燕 來源: 程序猿技術充電站
相關推薦

2023-09-08 12:19:01

線程方法interrupt

2022-02-28 07:01:22

線程中斷interrupt

2025-02-17 00:00:25

Java并發編程

2025-02-19 00:05:18

Java并發編程

2011-12-29 13:31:15

Java

2025-01-10 07:10:00

2019-11-07 09:20:29

Java線程操作系統

2024-12-31 09:00:12

Java線程狀態

2025-02-03 08:23:33

2023-10-08 09:34:11

Java編程

2024-10-21 18:12:14

2023-10-18 15:19:56

2022-11-09 09:01:08

并發編程線程池

2019-09-16 08:45:53

并發編程通信

2025-02-03 00:40:00

線程組Java并發編程

2022-03-31 07:52:01

Java多線程并發

2010-02-24 10:24:10

Python線程

2017-09-19 14:53:37

Java并發編程并發代碼設計

2017-01-10 13:39:57

Python線程池進程池

2023-09-26 10:30:57

Linux編程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 97精品视频在线观看 | 日韩中文字幕在线播放 | 在线看片网站 | 国产综合在线视频 | 天天操天天干天天透 | 日韩性在线| 97久久久久久久久 | 911网站大全在线观看 | 特黄特色大片免费视频观看 | 久久久精品视频免费 | 91精品久久久久久久久久 | 一区二区三区免费 | 国产精品一区在线观看你懂的 | 日日夜夜草 | 国产精品自拍视频 | 国产精品18久久久久久久 | 欧美黄色网络 | 蜜臀久久 | 欧美日韩a | 国产精品久久久久久中文字 | 欧美性极品xxxx做受 | 免费中文字幕 | 日本一区高清 | 国产精品久久久久久一区二区三区 | 成人毛片在线视频 | 观看av| 成人免费区一区二区三区 | 亚洲精品高清视频 | 紧缚调教一区二区三区视频 | 综合二区 | 狠狠av| 亚洲国产成人精品女人久久久 | 国产高清免费视频 | 国产盗摄视频 | 久久99深爱久久99精品 | 国产成人免费视频网站视频社区 | 欧美视频二区 | 亚洲国产精品久久久久婷婷老年 | 久久精品欧美视频 | 毛片99 | 夜夜爽99久久国产综合精品女不卡 |