併發編程之線程中斷interrupt

線程中斷可能在平時的開發中我們用的不多,但是我相信大部分都見過InterruptedException,因爲不管我們在調用object.wait()還是Thread.sleep()都會拋出一個InterruptedException。可能有很多人都是直接的繼續拋出去或者不做任何處理直接打印堆棧信息,當然有可能這樣沒有問題,但是有些業務我們這樣處理並不適合。要弄懂這些,我們就需要知道interrupt的作用是什麼。

一、如何讓線程停止

如果我們需要讓一個線程停止,我們可以用什麼方法呢?我們今天先不討論那種過時的stop方法,這也不推薦使用,原因是因爲太暴力了,它會直接將線程中斷,不管你的線程資源是否釋放。我在這裏描述過我見過的其他兩種方式:

  • 自己定義一個volatile的boolean類型的變量控制
  • 使用thread.interrupt

1、使用自定義的volatile的boolean變量控制

package com.taolong.concurrent.interrupt;

/**
 * @Author
 * @Version 1.0
 * @Description 自己控制線程終端
 */
public class MyselfInterruptTest {


    public static void main(String[] args) {

        MyThread task = new MyThread();

        task.start();

        //先讓task跑10秒鐘
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //終止task
        task.setStop(true);

    }




    static class MyThread extends Thread{
        private volatile boolean stop;

        @Override
        public void run() {
            while(!stop){
                System.out.println("continue run.....");
            }
            System.out.println("--->i will stop...");
        }


        public boolean isStop() {
            return stop;
        }

        public void setStop(boolean stop) {
            this.stop = stop;
        }
    }
}

2、使用自帶的interrupt

package com.taolong.concurrent.interrupt;

/**
 * @Version 1.0
 * @Description 使用interrupt終止
 */
public class UseInterruptTest {

    public static void main(String[] args) {
        Thread task = new Thread(new MyInterruptRunnable());

        task.start();

        //先讓task跑10秒鐘
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //終止task
        task.interrupt();
    }

    static class MyInterruptRunnable implements Runnable{

        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("continue run.....");
            }
            System.out.println("--->i will stop...");
        }

    }
}

兩個執行的結果是一樣的,都是執行10s後終止
在這裏插入圖片描述

雖然執行的結果相同,但是這只是簡單的使用,後面我們繼續看一下稍微改變一下代碼看看會發生什麼變化。

我們現在讓任務每隔3s打印一次,修改的代碼如下:

public void run() {
    while(!Thread.currentThread().isInterrupted()){
        System.out.println("continue run.....");
        //每隔3秒打印一次
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("--->i will stop...");
}

(1)使用自定義的變量控制打印的結果
在這裏插入圖片描述
(2)使用interrupt控制的打印結果
在這裏插入圖片描述
這個時候兩個打印的結果就不一樣了,而且使用interrupt中斷的時候,下面並沒有中斷任務。爲什麼會產生這樣的情況呢?如果不太瞭解interrupt的使用,是不是容易出錯(如果我們真的在這裏需要中斷業務)。

二、Interrupt使用的正確姿勢

前面我們通過兩種方法來實現線程中斷的操作,可以看出來基本上都能夠滿足,但是還是有一些差別,尤其是對interrupt的使用,可能會出現跟我們預期的不一致的情況。現在我們就來看看Interrupt到底如何使用。

1、首先需要理解的是interrupt主要是給線程發送一箇中斷信號,並不會強迫線程立即停止(這也是爲什麼不使用stop的原因)—理解這一點非常重要

從我們上面的例子可以看出,我們通過在run方法中增加if的邏輯判斷去控制線程的中斷,如果讓我們不加這個if判斷的時候,即便我們調用了thread.interrupt()方法,線程也不會中斷,而是會繼續執行,感興趣的可以自行編寫代碼進行測試。

2、線程中斷相關的幾個方法的介紹和使用interrupt()、isInterrupted()、interrupted()

interrupt():向線程發送一箇中斷信號標誌,至於是否真正中斷,要看該線程的具體邏輯

isInterrupted():判斷線程是否中斷標誌,其實就是獲取了這個中斷標誌的值

interrupted():獲取了線程的中斷信號,但是會清除中斷標誌位,比如一開始中斷標誌位爲false,後面調用interrupt()將中斷標誌位設置成true,此時如果調用interrupted()返回的true,但是同時它會將中斷標誌位置爲false,那麼下次不管調用isInterrupted()或者是interrupted()返回的都是false

雖然上面三個方法可以控制中斷標誌位,但是它僅僅是一個變量,它不會直接影響我們的線程的中斷。我們必須要自己根據這個變量去控制線程的中斷與否。

前面一節的例子中已經說明了interrupt()、isInterrupted()的基本使用,其實可以當作一個基本的boolean類型判斷即可。當調用interrupt()產生異常時,則不能將標誌位置爲true,我們通過代碼打印看一下

package com.taolong.concurrent.interrupt;

/**
 * @Version 1.0
 * @Description 使用interrupt終止
 */
public class UseInterruptTest {

    public static void main(String[] args) {
        Thread task = new Thread(new MyInterruptRunnable());

        task.start();

        //先讓task跑10秒鐘
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //發起線程中斷信號
        task.interrupt();
    }

    static class MyInterruptRunnable implements Runnable{

        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("continue run.....");
                //每隔3秒打印一次
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //看看中斷標誌位結果
                    System.out.println("interrupt=" + Thread.currentThread().isInterrupted());
                }
            }
            System.out.println("--->i will stop...");
        }

    }
}

打印結果:
在這裏插入圖片描述
從日誌中可以看出,中斷標誌位是false(之前在main方法中的task.interrupt()沒生效),如果我們真的需要把正在thread中執行的任務終止掉怎麼辦呢,可以在catch中調用interrupt()方法,我們試試

catch (InterruptedException e) {
    e.printStackTrace();
    //看看中斷標誌位結果
    System.out.println("interrupt=" + Thread.currentThread().isInterrupted());
    Thread.currentThread().interrupt();
    System.out.println("after invoke interrupt() interrupt=" + Thread.currentThread().isInterrupted());
}

執行結果:
在這裏插入圖片描述
我們通過在catch中重新設置中斷標誌位,做到了終止線程的作用,並且中斷標誌位變成了true

這個時候可能有人會心裏想,這比使用自己定義的volatile的boolean類型的變量要麻煩一點,但是仔細思考真的是這樣子的嗎?—答案顯然不是如此,不然jdk也沒必要提供interrupt的方法了,比如可以看下面的場景:

1、自定義的volatile的boolean變量能及時相應在阻塞時中斷的操作嗎(比如當線程正在wait()、sleep()時)
2、自定義的volatile的boolean變量能檢測出中斷異常、並且根據異常判斷任務是否真的需要被中斷嗎

這個時候發現自定義的變量控制線程中斷只能滿足一些比較簡單的業務,而使用interrupt不僅覆蓋了自定義變量的功能,而且還有更強大的功能,所以我們在選擇上,建議儘量選擇interrupt。

上面還有一個interrupted()方法沒有寫測試用例,原本想感興趣的自行測試,在這裏也簡單的貼一下代碼吧

while(!Thread.currentThread().isInterrupted()){
    System.out.println("continue run.....");
    //每隔3秒打印一次
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
        //看看中斷標誌位結果
        System.out.println("interrupt=" + Thread.currentThread().isInterrupted());
        Thread.currentThread().interrupt();//true
        System.out.println("after invoke interrupt() interrupt=" + Thread.currentThread().isInterrupted());
        //使用interrupted(),查看中斷標誌位
        System.out.println("使用interrupted()判斷是否中斷 interrupt=" + Thread.interrupted());//返回true,但是會清除,下一次調用爲false
        //調用interrupted()後,查看是否會清除標誌位
        System.out.println("after invoke interrupted() interrupt=" + Thread.currentThread().isInterrupted());//false
    }
}

打印結果:
在這裏插入圖片描述
這裏對結果稍微解析一下:

1、首先interrupt=false,說明在主線程調用t.interrupt()產生的異常導致並沒有中斷線程,而且走到了catch

2、當在catch再次調用interrupt()時,表示將中斷標註位置爲了true

3、當interrupted()判斷中斷標誌位=true—2產生的結果

4、當interrupted()調用後會清除中斷標誌位=false----3調用intterupted()清除了標誌位

5、此時線程被interrupted()清除後,線程標誌位依然是false,while循環判斷依然=true,則最終線程未中斷

相信大家對interrupt的使用已經比較熟悉了,後面可以根據自己的業務情況判斷是否真的需要中斷線程哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章