首頁  >  文章  >  Java  >  怎麼在Java中終止一個線程

怎麼在Java中終止一個線程

WBOY
WBOY轉載
2023-04-26 08:58:201045瀏覽

Thread.stop被禁用之謎

問道怎麼終止一個線程,可能大多數人都知道可以呼叫Thread.stop方法。

但這個方法從jdk1.2之後就不建議使用了,為什麼不建議使用呢?

我們先來看下這個方法的定義:

  @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這個方法首先偵測有沒有執行緒存取的權限。如果有權限的話,來判斷當前的線程是否是剛剛創建的線程,如果不是剛剛創建的,那麼就調用resume方法來解除線程的暫停狀態。

最後呼叫stop0方法來結束執行緒。

其中resume和stop0是兩個native的方法,具體的實作這裡就不講了。

看起來stop方法很合理,沒有什麼問題。那為什麼說這個方法是不安全的呢?

接下來我們來看一個例子。

我們建立一個NumberCounter的類,這個類別有一個increaseNumber的安全方法,用來對number加一:

public class NumberCounter {
    //要保存的数字
    private volatile int number=0;
    //数字计数器的逻辑是否完整
    private volatile boolean flag = false;

    public synchronized int increaseNumber() throws InterruptedException {
        if(flag){
            //逻辑不完整
            throw new RuntimeException("逻辑不完整,数字计数器未执行完毕");
        }
        //开始执行逻辑
        flag = true;
        //do something
        Thread.sleep(5000);
        number++;
        //执行完毕
        flag=false;
        return number;
    }
}

事實上,在實際工作中這樣的方法可能需要執行比較久的時間,所以這裡我們透過呼叫Thread.sleep來模擬這個耗時操作。

這裡我們還有一個flag參數,來標誌這個increaseNumber方法是否成功執行完畢。

好了,接下來我們在一個線程中調用這個類別的方法,看看會發生什麼:

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

這裡,我們創建了一個線程,等這個線程運行3秒鐘之後,直接呼叫thread.stop方法,結果我們發現出現了下面的例外:

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

##.stop方法方法方法方法直接終止了執行緒的運行,導致mberCounter.increaseNumber未執行完畢。

但是這個未執行完畢的狀態是隱藏的,如果使用thread.stop方法來終止線程,很有可能導致未知的結果。

所以,我們說thread.stop是不安全的。

怎麼才能安全

那麼,如果不呼叫thread.stop方法,怎麼才能安全的終止執行緒呢?

所謂安全,就是需要讓執行緒裡面的邏輯執行完畢,而不是執行一半。

為了達到這個效果,Thread為我們提供了三個比較類似的方法,他們分別是interrupt、interrupted和isInterrupted。

    interrupt是為執行緒設定中斷標誌;interrupted是偵測中斷並清除中斷狀態;isInterrupted只偵測中斷。還有重要的一點就是interrupted是類別方法,作用於目前線程,interrupt和isInterrupted作用於此線程,也就是程式碼中呼叫此方法的實例所代表的執行緒。
  • interrupt就是中斷的方法,它的工作流程如下:

  • #如果目前執行緒實例在呼叫Object類別的wait(),wait(長)或wait( long,int)方法或join(),join(long),join(long,int)方法,或在該實例中呼叫了Thread.sleep(long)或Thread.sleep(long,int)方法,並且正在阻塞狀態中時,則其中斷狀態將被清除,並將收到InterruptedException。

  • 如果此執行緒在InterruptibleChannel上的I/O操作中處於被阻塞​​狀態,則該channel將關閉,該執行緒的中斷狀態將被設為true,並且該執行緒將收到java.nio.channels.ClosedByInterruptException異常。

  • 如果此執行緒在java.nio.channels.Selector中處於被阻塞​​狀態,則會設定該執行緒的中斷狀態為true,並且它將立即從select操作中傳回。

如果上面的情況都不成立,則設定中斷狀態為true。

在上面的範例中,NumberCounter的increaseNumber方法中,我們呼叫了Thread.sleep方法,所以如果在這個時候,呼叫了thread的interrupt方法,執行緒就會拋出一個InterruptedException異常。

###我們把上面呼叫的例子改成下面這樣:###
    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();
    }
###運行之後再試一次:###

Exception in thread "main" Exception in thread "Thread-0" java.lang.RuntimeException: 逻辑不完整,数字计数器未执行完毕
    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

可以看到,我们捕获到了这个InterruptedException,并且得知具体的原因是sleep interrupted。

捕获异常之后的处理

从上面的分析可以得知,thread.stop跟thread.interrupt的表现机制是不一样的。thread.stop属于悄悄终止,我们程序不知道,所以会导致数据不一致,从而产生一些未知的异常。

而thread.interrupt会显示的抛出InterruptedException,当我们捕捉到这个异常的时候,我们就知道线程里面的逻辑在执行的过程中受到了外部作用的干扰,那么我们就可以执行一些数据恢复或者数据校验的动作。

在上面的代码中,我们是捕获到了这个异常,打印出异常日志,然后向上抛出一个RuntimeException。

正常情况下我们是需要在捕获异常之后,进行一些处理。

那么自己处理完这个异常之后,是不是就完美了呢?

答案是否定的。

因为如果我们自己处理了这个InterruptedException, 那么程序中其他部分如果有依赖这个InterruptedException的话,就可能会出现数据不一致的情况。

所以我们在自己处理完InterruptedException之后,还需要再次抛出这个异常。

怎么抛出InterruptedException异常呢?

有两种方式,第一种就是在调用Thread.interrupted()清除了中断标志之后立即抛出:

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

还有一种方式就是,在捕获异常之后,调用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();
  }
}

这两种方式都能达到预想的效果。

以上是怎麼在Java中終止一個線程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡[email protected]刪除