欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文揭曉如何在Java中終止一個(gè)線程

 更新時(shí)間:2023年03月09日 14:10:14   作者:flydean程序那些事  
工作中我們經(jīng)常會(huì)用到線程,一般情況下我們讓線程執(zhí)行就完事了,那么你們有沒(méi)有想過(guò)如何去終止一個(gè)正在運(yùn)行的線程呢?本文就來(lái)帶大家一起看看

簡(jiǎn)介

工作中我們經(jīng)常會(huì)用到線程,一般情況下我們讓線程執(zhí)行就完事了,那么你們有沒(méi)有想過(guò)如何去終止一個(gè)正在運(yùn)行的線程呢?

今天帶大家一起來(lái)看看。

Thread.stop被禁用之謎

問(wèn)道怎么終止一個(gè)線程,可能大多數(shù)人都知道可以調(diào)用Thread.stop方法。

但是這個(gè)方法從jdk1.2之后就不推薦使用了,為什么不推薦使用呢?

我們先來(lái)看下這個(gè)方法的定義:

  @Deprecated(since="1.2")
    public final void stop() {
        @SuppressWarnings("removal")
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            checkAccess();
            if (this != Thread.currentThread()) {
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }
        // A zero status value corresponds to "NEW", it can't change to
        // not-NEW because we hold the lock.
        if (threadStatus != 0) {
            resume(); // Wake up thread if it was suspended; no-op otherwise
        }

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }

從代碼我們可以看出,stop這個(gè)方法首先檢測(cè)有沒(méi)有線程訪問(wèn)的權(quán)限。如果有權(quán)限的話,來(lái)判斷當(dāng)前的線程是否是剛剛創(chuàng)建的線程,如果不是剛剛創(chuàng)建的,那么就調(diào)用resume方法來(lái)解除線程的暫停狀態(tài)。

最后調(diào)用stop0方法來(lái)結(jié)束線程。

其中resume和stop0是兩個(gè)native的方法,具體的實(shí)現(xiàn)這里就不講了。

看起來(lái)stop方法很合理,沒(méi)有什么問(wèn)題。那么為什么說(shuō)這個(gè)方法是不安全的呢?

接下來(lái)我們來(lái)看一個(gè)例子。

我們創(chuàng)建一個(gè)NumberCounter的類,這個(gè)類有一個(gè)increaseNumber的安全方法,用來(lái)對(duì)number加一:

public class NumberCounter {
    //要保存的數(shù)字
    private volatile int number=0;
    //數(shù)字計(jì)數(shù)器的邏輯是否完整
    private volatile boolean flag = false;

    public synchronized int increaseNumber() throws InterruptedException {
        if(flag){
            //邏輯不完整
            throw new RuntimeException("邏輯不完整,數(shù)字計(jì)數(shù)器未執(zhí)行完畢");
        }
        //開(kāi)始執(zhí)行邏輯
        flag = true;
        //do something
        Thread.sleep(5000);
        number++;
        //執(zhí)行完畢
        flag=false;
        return number;
    }
}

事實(shí)上,在實(shí)際工作中這樣的方法可能需要執(zhí)行比較久的時(shí)間,所以這里我們通過(guò)調(diào)用Thread.sleep來(lái)模擬這個(gè)耗時(shí)操作。

這里我們還有一個(gè)flag參數(shù),來(lái)標(biāo)志這個(gè)increaseNumber方法是否成功執(zhí)行完畢。

好了,接下來(lái)我們?cè)谝粋€(gè)線程中調(diào)用這個(gè)類的方法,看看會(huì)發(fā)生什么:

    public static void main(String[] args) throws InterruptedException {
        NumberCounter numberCounter= new NumberCounter();
        Thread thread = new Thread(()->{
            while (true){
                try {
                    numberCounter.increaseNumber();
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(3000);
        thread.stop();
        numberCounter.increaseNumber();
    }

這里,我們創(chuàng)建了一個(gè)線程,等這個(gè)線程運(yùn)行3秒鐘之后,直接調(diào)用thread.stop方法,結(jié)果我們發(fā)現(xiàn)出現(xiàn)了下面的異常:

Exception in thread "main" java.lang.RuntimeException: 邏輯不完整,數(shù)字計(jì)數(shù)器未執(zhí)行完畢
    at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
    at com.flydean.Main.main(Main.java:18)

這是因?yàn)閠hread.stop方法直接終止了線程的運(yùn)行,導(dǎo)致mberCounter.increaseNumber未執(zhí)行完畢。

但是這個(gè)未執(zhí)行完畢的狀態(tài)是隱藏的,如果使用thread.stop方法來(lái)終止線程,很有可能導(dǎo)致未知的結(jié)果。

所以,我們說(shuō)thread.stop是不安全的。

怎么才能安全

那么,如果不調(diào)用thread.stop方法,怎么才能安全的終止線程呢?

所謂安全,那就是需要讓線程里面的邏輯執(zhí)行完畢,而不是執(zhí)行一半。

為了實(shí)現(xiàn)這個(gè)效果,Thread為我們提供了三個(gè)比較類似的方法,他們分別是interrupt、interrupted和isInterrupted。

interrupt是給線程設(shè)置中斷標(biāo)志;interrupted是檢測(cè)中斷并清除中斷狀態(tài);isInterrupted只檢測(cè)中斷。還有重要的一點(diǎn)就是interrupted是類方法,作用于當(dāng)前線程,interrupt和isInterrupted作用于此線程,即代碼中調(diào)用此方法的實(shí)例所代表的線程。

interrupt就是中斷的方法,它的工作流程如下:

  • 如果當(dāng)前線程實(shí)例在調(diào)用Object類的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)方法,或者在該實(shí)例中調(diào)用了Thread.sleep(long)或Thread.sleep(long,int)方法,并且正在阻塞狀態(tài)中時(shí),則其中斷狀態(tài)將被清除,并將收到InterruptedException。
  • 如果此線程在InterruptibleChannel上的I/O操作中處于被阻塞狀態(tài),則該channel將被關(guān)閉,該線程的中斷狀態(tài)將被設(shè)置為true,并且該線程將收到j(luò)ava.nio.channels.ClosedByInterruptException異常。
  • 如果此線程在java.nio.channels.Selector中處于被被阻塞狀態(tài),則將設(shè)置該線程的中斷狀態(tài)為true,并且它將立即從select操作中返回。
  • 如果上面的情況都不成立,則設(shè)置中斷狀態(tài)為true。

在上面的例子中,NumberCounter的increaseNumber方法中,我們調(diào)用了Thread.sleep方法,所以如果在這個(gè)時(shí)候,調(diào)用了thread的interrupt方法,線程就會(huì)拋出一個(gè)InterruptedException異常。

我們把上面調(diào)用的例子改成下面這樣:

    public static void main(String[] args) throws InterruptedException {
        NumberCounter numberCounter = new NumberCounter();

        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    numberCounter.increaseNumber();
                } catch (InterruptedException e) {
                    System.out.println("捕獲InterruptedException");
                    throw new RuntimeException(e);
                }
            }
        });

        thread.start();
        Thread.sleep(500);
        thread.interrupt();
        numberCounter.increaseNumber();
    }

運(yùn)行之后再試一次:

Exception in thread "main" Exception in thread "Thread-0" java.lang.RuntimeException: 邏輯不完整,數(shù)字計(jì)數(shù)器未執(zhí)行完畢
    at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
    at com.flydean.Main2.main(Main2.java:21)
java.lang.RuntimeException: java.lang.thread.interrupt: sleep interrupted
    at com.flydean.Main2.lambda$main$0(Main2.java:13)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:17)
    at com.flydean.Main2.lambda$main$0(Main2.java:10)
    ... 1 more
捕獲InterruptedException

可以看到,我們捕獲到了這個(gè)InterruptedException,并且得知具體的原因是sleep interrupted。

捕獲異常之后的處理

從上面的分析可以得知,thread.stop跟thread.interrupt的表現(xiàn)機(jī)制是不一樣的。thread.stop屬于悄悄終止,我們程序不知道,所以會(huì)導(dǎo)致數(shù)據(jù)不一致,從而產(chǎn)生一些未知的異常。

而thread.interrupt會(huì)顯示的拋出InterruptedException,當(dāng)我們捕捉到這個(gè)異常的時(shí)候,我們就知道線程里面的邏輯在執(zhí)行的過(guò)程中受到了外部作用的干擾,那么我們就可以執(zhí)行一些數(shù)據(jù)恢復(fù)或者數(shù)據(jù)校驗(yàn)的動(dòng)作。

在上面的代碼中,我們是捕獲到了這個(gè)異常,打印出異常日志,然后向上拋出一個(gè)RuntimeException。

正常情況下我們是需要在捕獲異常之后,進(jìn)行一些處理。

那么自己處理完這個(gè)異常之后,是不是就完美了呢?

答案是否定的。

因?yàn)槿绻覀冏约禾幚砹诉@個(gè)InterruptedException, 那么程序中其他部分如果有依賴這個(gè)InterruptedException的話,就可能會(huì)出現(xiàn)數(shù)據(jù)不一致的情況。

所以我們?cè)谧约禾幚硗闕nterruptedException之后,還需要再次拋出這個(gè)異常。

怎么拋出InterruptedException異常呢?

有兩種方式,第一種就是在調(diào)用Thread.interrupted()清除了中斷標(biāo)志之后立即拋出:

   if (Thread.interrupted())  // Clears interrupted status!
       throw new InterruptedException();

還有一種方式就是,在捕獲異常之后,調(diào)用Thread.currentThread().interrupt()再次中斷線程。

public void run () {
  try {
    while (true) {
      // do stuff
    }
  }catch (InterruptedException e) {
    LOGGER.log(Level.WARN, "Interrupted!", e);
    // Restore interrupted state...
    Thread.currentThread().interrupt();
  }
}

這兩種方式都能達(dá)到預(yù)想的效果。

總結(jié)

線程不能調(diào)用stop來(lái)終止主要是因?yàn)椴粫?huì)拋出異常,從而導(dǎo)致一些安全和數(shù)據(jù)不一致的問(wèn)題。所以,最好的方式就是調(diào)用interrupt方法來(lái)處理。

到此這篇關(guān)于一文揭曉如何在Java中終止一個(gè)線程的文章就介紹到這了,更多相關(guān)Java終止線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論