天天看點

Java多線程系列篇之中斷機制引入中斷協商機制中斷Demo源碼分析生産案例

引入

螞蟻金服面試題:如何停止、中斷一個運作中的線程?

Java多線程系列篇之中斷機制引入中斷協商機制中斷Demo源碼分析生産案例

如果直接回答 interrupt 那就爆炸了💥,答案絕對是錯誤的,這就是對底層運作機制的不了解,是以我們首先要搞清楚這幾個API的異同點

方法名 傳回值 作用描述
interrupt void 一個執行個體方法,僅僅是将置标志符為true,不會停止線程
interrupted boolean(靜态方法)

判斷此線程是否被中斷,并且清除目前中斷狀态,這個方法主要做了2件事:

1、傳回目前線程的中斷狀态

2、将目前線程的中斷狀态設定為false

*這個方法有點不好了解,因為連續調用2次的結果可能不一樣

isInterrupted boolean 判斷目前線程是否被中斷(通過檢測中斷辨別位置)

中斷協商機制

首先,一個線程不應該由其他線程來強制中斷或者停止,而是應該由線程自己自行停止

是以,Thread.stop、Thread.suspend、Thread.resume 都已經廢棄了

其次,在Java中沒有辦法立刻停止一條線程,然而停止線程卻是很重要的,如取消一個耗時的操作。是以,Java提供了一種用于停止線程的機制--中斷

中斷協商機制并不是立刻+馬上+now,stop一個線程

中斷隻是一種協商機制,Java并沒有給中斷增加任何文法,中斷的過程完全是需要程式員自行實作。若需要中斷一個線程,你需要手動調用該線程的Interrupt方法,該方法也僅僅是将線程對象中斷的辨別為設定為True;接着你需要自己寫代碼不斷的檢測目前線程的表示為,如果為True表示為中斷,為false表示為未中斷;

通過調用線程對象的interrupt方法将該線程的辨別位置為true,可以在别的線程中調用,也可以在自己的線程中調用

中斷Demo

volatile實作

通過volatile的可見性實作線程中斷

AtomicBoolean實作

通過原子類進行實作

Thread類API實作

通過interrupt進行實作

package com.gzczy.concurrent.thread.interrupted;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @Description 停止線程三種方法
 * @Author chenzhengyu
 * @Date 2021-03-21 15:22
 */
public class STWThreadDemo {

    private static volatile boolean flag = true;

    private static AtomicBoolean atomicBoolean = new AtomicBoolean(true);

    public static void main(String[] args) throws Exception{
        Thread t1 = new Thread(() -> {
            //demo1();
            //demo2();
            demo3();
        },"t1");
        t1.start();
        TimeUnit.SECONDS.sleep(5);
        new Thread(()->{
            //flag = false;
            //atomicBoolean.compareAndSet(true,false);
            t1.interrupt();
        }).start();
    }

    /**
     * 第一種:使用volatile關鍵字進行停止
     */

    public static void demo1(){
        while (flag){
            System.out.println("正在運作");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 第二種:通過原子類
     */
    public static void demo2(){
        while (atomicBoolean.get()){
            System.out.println("正在運作");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 第三種:通過interrupt方法進行打斷
     */
    public static void demo3(){
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("正在運作");
        }
    }
}      

源碼分析

Java多線程系列篇之中斷機制引入中斷協商機制中斷Demo源碼分析生産案例
Java多線程系列篇之中斷機制引入中斷協商機制中斷Demo源碼分析生産案例

具體來說,當對一個線程,調用interrupt()時

  • 如果線程處于正常活躍的狀态,那麼将會該線程的中斷辨別設定為true,也就僅此而已,被設定中斷辨別的線程會繼續運作,不受影響。是以調用調用interrupt()時,需要被調用的線程自己進行配合才行
  • 如果線程處于阻塞狀态(如圖1代碼所示)sleep、wait、join等狀态,在别的線程中調用目前對象的interrupt()時,線程會立刻退出阻塞狀态,并且抛出InterruptedException異常

生産案例

故障代碼

在生産上,我們不能單純的以為Interrupt能夠中斷線程,這隻是一種協商機制,請看下面案例

package com.gzczy.concurrent.thread.interrupted;

import java.util.concurrent.TimeUnit;

/**
 * @Description 線程中斷demo
 * @Author chenzhengyu
 * @Date 2021-03-21 15:49
 */
public class InterruptErrorDemo {

    public static void main(String[] args) throws Exception{
        Thread t1 = new Thread(() -> {
            CanNotStop();
        }, "t1");
        t1.start();
        TimeUnit.SECONDS.sleep(3);
        Thread t2 = new Thread(()->{
            t1.interrupt();
        },"t2");
        t2.start();
    }

    /**
     * 抛出異常後仍然 無法正常打斷
     */
    public static void CanNotStop(){
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Running.....");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}      

運作結果

Running.....
Running.....
Running.....
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.gzczy.concurrent.thread.interrupted.InterruptErrorDemo.CanNotStop(InterruptErrorDemo.java:31)
    at com.gzczy.concurrent.thread.interrupted.InterruptErrorDemo.lambda$main$0(InterruptErrorDemo.java:14)
    at java.lang.Thread.run(Thread.java:748)
Running.....
Running.....
Running.....
Running.....      

通過運作結果我們可以觀察到,運作完成就抛出了異常,然後就一直停不下來了。這種情況會導緻在生産實際情況下的系統卡頓,因為程式無法正常終止。在代碼中,我們翻開interrupt源碼可以發現這句話:當我們打斷正在睡眠的線程時候,我們會清除打斷标記,由于t2剛剛是發起了打斷請求,設定打斷辨別為true,但是因為有睡眠代碼塊,打斷時候會把打斷辨別清空,導緻無法進入break代碼塊,即便是異常也沒有停,是以我們需要将線程中斷标記位置改為true

正确代碼

我們在剛剛catch代碼塊再次打斷,複位标志位,使其能夠正确的中斷線程

package com.gzczy.concurrent.thread.interrupted;

import java.util.concurrent.TimeUnit;

/**
 * @Description 線程中斷demo
 * @Author chenzhengyu
 * @Date 2021-03-21 15:49
 */
public class InterruptErrorDemo {

    public static void main(String[] args) throws Exception{
        Thread t1 = new Thread(() -> {
            CanNotStop();
        }, "t1");
        t1.start();
        TimeUnit.SECONDS.sleep(3);
        Thread t2 = new Thread(()->{
            t1.interrupt();
        },"t2");
        t2.start();
    }

    /**
     * 抛出異常後仍然 無法正常打斷(原因請點選進入API檢視,sleep wait join方法打斷會清除打斷辨別)
     * 在異常部分需要再次打斷 重置标志位
     */
    public static void CanNotStop(){
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Running.....");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                //我們在剛剛catch代碼塊再次打斷,複位标志位,使其能夠正确的中斷線程
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
    }
}      

方法加深了解

判斷下列代碼輸出的結果

package com.gzczy.concurrent.thread.interrupted;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description 對比打斷方法中間
 * @Author chenzhengyu
 * @Date 2021-03-21 16:14
 */
@Slf4j(topic = "c.InterruptCompare")
public class InterruptCompare {

    public static void main(String[] args) {
        log.info("==>"+Thread.interrupted());
        log.info("==>"+Thread.interrupted());
        log.info("========================");
        Thread.currentThread().interrupt();
        log.info("========================");
        //通過觀察結果 我們可以看到這裡顯示為true 因為剛剛打斷了,與此同時會将打斷狀态清空,下次判斷為false
        log.info("==>"+Thread.interrupted());
        // 此方法并不會重置辨別位置,隻是單純的顯示一下目前狀态
        log.info("==>"+Thread.currentThread().isInterrupted());
        log.info("==>"+Thread.interrupted());
    }
}
      

結果

16:47:54.803 [main] c.InterruptCompare - ==>false
16:47:54.806 [main] c.InterruptCompare - ==>false
16:47:54.806 [main] c.InterruptCompare - ========================
16:47:54.806 [main] c.InterruptCompare - ========================
16:47:54.806 [main] c.InterruptCompare - ==>true
16:47:54.806 [main] c.InterruptCompare - ==>false
16:47:54.806 [main] c.InterruptCompare - ==>false      

底層源碼分析

log.info("==>"+Thread.interrupted());

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}      

log.info("==>"+Thread.currentThread().isInterrupted());

public boolean isInterrupted() {
        return isInterrupted(false);
    }      

通過方法觀察我們可以看到清晰的表達了”中斷狀态将會傳入的ClearInterrupted參數值确定是否需要重置“

由此可以得出結論:

靜态方法Interrupted将會清除中斷狀态(傳入參數的ClearInterrupted為true)

執行個體方法isInterrupted則不會(傳入參數的ClearInterrupted為false)