自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java并發(fā)編程:如何正確停止線程

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

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

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

  • 用戶主動取消執(zhí)行;
  • 線程在運行時發(fā)生錯誤或超時,需要停止;
  • 服務(wù)需要立即關(guān)閉。

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

2. 如何正確停止線程?

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

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

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

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

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

舉個生活中的例子:

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

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

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

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

4. 代碼實踐

4.1. 錯誤的線程停止方式

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

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();
    }
}

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

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

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

出于安全考慮,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

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

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

當(dāng)指定線程被中斷時,在線程內(nèi)部調(diào)用Thread.currentThread().isInterrupted()會返回true,可以根據(jù)此進行中斷后的處理邏輯。

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();
    }
}

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

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

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

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

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

例如,使用Thread.sleep(1)模擬每次搬家所需的時間。在主線程中,等待 3ms 后中斷,因此預(yù)計在搬完 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

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

為什么會拋出異常?

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

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

4.5. 當(dāng)sleep方法與isInterrupted結(jié)合使用時會發(fā)生什么?

你注意到在示例 3 的代碼中,我們在捕獲異常后使用了break來主動結(jié)束循環(huán)嗎?那么,我們是否可以在catch中不使用break,而是在循環(huán)入口處判斷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();
    }
}

輸出(你可能需要調(diào)整主線程執(zhí)行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后,它繼續(xù)搬了第四和第五批物品?

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

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

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

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方法?

如前所述,當(dāng)中斷發(fā)生并拋出InterruptedException時,isInterrupted的結(jié)果會被重置為false。但是,支持再次調(diào)用interrupt,這會使isInterrupted的結(jié)果變?yōu)閠rue。

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

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

這樣可以避免在catch代碼塊中處理業(yè)務(wù)邏輯!

4.7 判斷中斷是否發(fā)生的方法

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

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

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

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

        // 中斷當(dāng)前線程
        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()會重置中斷標志,因此最后的輸出結(jié)果變?yōu)閒alse。

5. JDK 內(nèi)置的可以響應(yīng)中斷的方法

主要有以下方法可以響應(yīng)中斷并拋出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的相關(guān)方法
  10. java.nio.channels.Selector的相關(guān)方法
責(zé)任編輯:武曉燕 來源: 程序猿技術(shù)充電站
相關(guān)推薦

2023-09-08 12:19:01

線程方法interrupt

2022-02-28 07:01:22

線程中斷interrupt

2025-02-17 00:00:25

Java并發(fā)編程

2025-02-19 00:05:18

Java并發(fā)編程

2011-12-29 13:31:15

Java

2025-01-10 07:10:00

2019-11-07 09:20:29

Java線程操作系統(tǒng)

2024-12-31 09:00:12

Java線程狀態(tài)

2025-02-03 08:23:33

2023-10-08 09:34:11

Java編程

2025-02-03 00:40:00

線程組Java并發(fā)編程

2019-09-16 08:45:53

并發(fā)編程通信

2022-11-09 09:01:08

并發(fā)編程線程池

2023-10-18 15:19:56

2022-03-31 07:52:01

Java多線程并發(fā)

2024-10-21 18:12:14

2017-09-19 14:53:37

Java并發(fā)編程并發(fā)代碼設(shè)計

2023-10-18 09:27:58

Java編程

2017-01-10 13:39:57

Python線程池進程池

2023-09-26 10:30:57

Linux編程
點贊
收藏

51CTO技術(shù)棧公眾號