java 多執行緒之使用 interrupt 停止執行緒的幾種方法

Airey發表於2018-09-18

停止執行緒

停止執行緒是java多執行緒開發中很重要的技術點,實際工作中很多業務都有類似的需求,掌握此技術可以對業務中所需停止的執行緒做有效的處理。但是停止執行緒在java語言中並不像break語句中那樣乾脆簡單,還需要我們做一下技巧性的處理。 如何更好的停止一個執行緒是我們應該要考慮的問題,停止一個執行緒意味著線上程處理完任務之前停掉正在做的操作,即放棄當前的操作。停止一個執行緒在之前老的JDK中使用的是Thread.stop()方法,但是後面發現這種處理方法是很危險而且不安全的,由於stop()方法已經在JDK中被標明是“作廢/過期”的方法,顯然它在功能上是具有缺陷的。作為一個負責的java工程師,最好是不要去使用它,因為使用stop()方法釋放鎖將會給資料造成不一致性的結果。如果出現這種情況,程式處理的資料可能遭到破壞,最終導致程式執行流程錯誤。 大多數停止一個執行緒的操作使用的是Thread.interrupt()方法,雖然方法名是“終止,停止”的意思,但是這個方法不會直接終止一個正在執行的執行緒還需要加入一個判斷才可以完成執行緒的停止。 此外,在java中有以下的3種方法可以終止正在執行的執行緒:

  1. 使用退出標誌,使執行緒正常退出,也就是當run方法完成後才停止;
  2. 就是我們上面所說的使用stop方法強行終止執行緒,是過期作廢的方法,這種方法可以排除不用;
  3. 使用interrupt方法終止執行緒。

第一種使用退出標誌的方法樓主不做介紹,具體可以參考其他部落格,都大同小異,樓主寫這篇部落格主要是探索使用interrupt停止執行緒的幾種方法的優劣。

異常法停止執行緒

首先我們用for-break方式停止執行緒:

public class ExceptionInterrupt extends Thread {

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 500000; i++) {
            if (this.isInterrupted()) {
                System.out.println("已經是停止狀態了!我要退出了!");
                break;
            }
            System.out.println("i=" + (i + 1));
        }
        System.out.println("我被輸出,如果此程式碼是for又繼續執行,執行緒並未停止!");
    }

    public static void main(String[] args) {
        ExceptionInterrupt thread = new ExceptionInterrupt();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}
複製程式碼

列印結果如下:

......
i=421200
i=421201
i=421202
i=421203
i=421204
i=421205
i=421206
i=421207
i=421208
i=421209
已經是停止狀態了!我要退出了!
我被輸出,如果此程式碼是for又繼續執行,執行緒並未停止!
end!
複製程式碼

我們發現,上面的示例雖然停止了執行緒,但是如果for語句下面還有語句,那麼還是會繼續執行。所以我們這裡用異常法來停止執行緒,就不會繼續執行for後面的業務邏輯了。

public class ExceptionInterrupt2 extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 500000; i++) {
                if (this.isInterrupted()) {
                    System.out.println("已經是停止狀態了!我要退出了!");
                    throw new InterruptedException();
                }
                System.out.println("i=" + (i + 1));
            }
            System.out.println("我在for下面");
        } catch (InterruptedException e) {
            System.out.println("進入ExceptionInterrupt2 類中run方法的catch了!");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ExceptionInterrupt2 thread = new ExceptionInterrupt2();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();

        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}

複製程式碼

列印結果如下:

......
i=96487
i=96488
i=96489
i=96490
i=96491
i=96492
i=96493
已經是停止狀態了!我要退出了!
進入ExceptionInterrupt2 類中run方法的catch了!
java.lang.InterruptedException
	at com.thread.chapter1.ExceptionInterrupt2.run(ExceptionInterrupt2.java:20)
end!
複製程式碼

在sleep()中停止執行緒

如果執行緒在sleep()狀態下停止執行緒,會有什麼效果呢?我們來看下面的示例:

public class SleepInterrupt extends Thread{

    @Override
    public void run() {
        super.run();
        try {
            System.out.println("run begin");
            Thread.sleep(200000);
            System.out.println("run end");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止!進入catch!"+this.isInterrupted());
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {

        try {
            SleepInterrupt thread=new SleepInterrupt();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }

        System.out.println("end");

    }

}
複製程式碼

執行結果如下:

run begin
end
在沉睡中被停止!進入catch!false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.thread.chapter1.SleepInterrupt.run(SleepInterrupt.java:18)
複製程式碼

從執行結果來看,如果在sleep狀態下停止某一執行緒,會進入catch語句,並且清除停止狀態值,使之變為false。上面的示例是先sleep然後再用interrupt()停止,與之相反的操作也要注意。

public class SleepInterrupt2 extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 100000; i++) {
                System.out.println("i=" + (i + 1));
            }
            System.out.println("run begin");
            Thread.sleep(200000);
            System.out.println("run end");
        } catch (InterruptedException e) {
            System.out.println("先停止,再遇到了sleep!進入catch!");
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        SleepInterrupt2 thread = new SleepInterrupt2();
        thread.start();
        thread.interrupt();
        System.out.println("end");

    }

}

複製程式碼

執行結果如下:

......
i=99991
i=99992
i=99993
i=99994
i=99995
i=99996
i=99997
i=99998
i=99999
i=100000
run begin
先停止,再遇到了sleep!進入catch!
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.thread.chapter1.SleepInterrupt2.run(SleepInterrupt2.java:21)

複製程式碼

使用return停止執行緒

將方法interrupted()與return結合使用也能實現停止執行緒的效果。

public class ReturnInterrupt extends Thread {

    @Override
    public void run() {
        while (true) {
            if (this.isInterrupted()) {
                System.out.println("停止了!");
                return;
            }
            System.out.println("time=" + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReturnInterrupt thread=new ReturnInterrupt();
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

複製程式碼

執行結果如下:

......
time=1536809054172
time=1536809054172
time=1536809054172
time=1536809054172
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
time=1536809054173
停止了!
複製程式碼

小結

上面幾種方法都是通過使用interrupt停止執行緒,只是做了不同的技巧處理,不過建議使用“拋異常”的方法來實現執行緒的停止,因為在catch塊中還可以將異常向上拋,使執行緒停止的事件得以傳播。

參考

《java多執行緒程式設計核心技術》

相關文章