天天看點

java中使用interrupt通知線程停止

使用 interrupt 來通知線程停止運作,而不是強制停止!

普通情況停止線程

public class RightWayStopThreadWithoutSleep implements Runnable {

    @Override
    public void run() {
        int num = 0;
        while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) {
            if (num % 10000 == 0) {
                System.out.println(num + "是1W的倍數");
            }
            num++;
        }
        System.out.println("任務運作結束!");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        // 等待1s
        Thread.sleep(1000);
        // 通知停止線程
        thread.interrupt();
    }
}      

使用 thread.interrupt() 通知線程停止

但是 線程需要配合:

在 while 中使用 Thread.currentThread().isInterrupted() 檢測線程目前的狀态

運作結果:

……
……
221730000是1W的倍數
221740000是1W的倍數
221750000是1W的倍數
221760000是1W的倍數
221770000是1W的倍數
221780000是1W的倍數
221790000是1W的倍數
221800000是1W的倍數
任務運作結束!

Process finished with exit code 0      

在可能被阻塞情況下停止線程

public class RightWayStopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數");
                }
                num++;
            }
            try {
                // 等個1秒,模拟阻塞
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("線程已停止!!");
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        // 等待時間要小于上面設定的1秒,不然線程都運作結束了,才執行到下面的thread.interrupt();代碼
        Thread.sleep(500);
        // 通知停止線程
        thread.interrupt();
    }
}      

線程在sleep 1秒的過程中,收到interrupt信号被打斷,

線程正在sleep過程中響應中斷的方式就是抛出 InterruptedException 異常

運作結果:

0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
線程已停止!!
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:19)
    at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0      

在每次疊代後都阻塞的情況下停止線程

public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 10000) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍數");
                    }
                    num++;
                    // 每次循環都要等待10毫秒,模拟阻塞
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        // 5秒後通知停止線程
        Thread.sleep(5000);
        thread.interrupt();
    }
}      

當每次疊代都會讓線程阻塞一段時間的時候,在 while/for 循環條件判斷時,

是不需要使用 * Thread.currentThread().isInterrupted() *判斷線程是否中斷的

運作結果:

0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
400是100的倍數
java.lang.InterruptedException: sleep interrupted      

如果将上述代碼中的 try/catch 放在 while 循環内

public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數");
                }
                num++;
                try {
                    // 每次循環都要等待10毫秒,模拟阻塞
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        // 5秒後通知停止線程
        Thread.sleep(5000);
        thread.interrupt();
    }
}      

運作結果:

0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
400是100的倍數
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at stopthreads.RightWayStopThreadWithSleepEveryLoop.lambda$main$0(RightWayStopThreadWithSleepEveryLoop.java:18)
    at java.lang.Thread.run(Thread.java:748)
500是100的倍數
600是100的倍數
700是100的倍數
……
……      

會發現雖然抛出了異常,但是程式并沒有停止,還在繼續輸出,

即使在 while 條件判斷處添加 !Thread.currentThread().isInterrupted() 條件,依然不能停止程式!

原因是

java語言在設計 sleep() 函數時,有這樣一個理念:

就是當它一旦響應中斷,便會把 interrupt 标記位清除。

也就是說,雖然線程在 sleep 過程中收到了 interrupt 中斷通知,并且也捕獲到了異常、列印了異常資訊,

但是由于 sleep 設計理念,導緻 Thread.currentThread().isInterrupted() 标記位會被清除,

是以才會導緻程式不能退出。

這裡如果要停止線程,隻需要在 catch 内 再調用一次 interrupt(); 方法

try {
    // 每次循環都要等待10毫秒,模拟阻塞
    Thread.sleep(10);
} catch (InterruptedException e) {
    e.printStackTrace();
    Thread.currentThread().interrupt();
}      

是以說,不要以為調用了 interrupt() 方法,線程就一定會停止。

兩種停止線程最佳方法

  1. 捕獲了 InterruptedException 之後的優先選擇:在方法簽名中抛出異常
public class RightWayStopThreadInProd implements Runnable {

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

    @Override
    public void run() {
        while (true) {
            System.out.println("go...");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                // 捕獲異常,進行儲存日志、停止程式等操作
                System.out.println("stop");
                e.printStackTrace();
            }
        }
    }

    /**
     * 如果方法内要抛出異常,最好是将異常抛出去,由頂層的調用方去處理,而不是try/catch
     * 這樣調用方才能捕獲異常并作出其它操作
     * @throws InterruptedException
     */
    private void throwInMethod() throws InterruptedException {
        Thread.sleep(2000);
    }
}      

如果方法内要抛出異常,最好是将異常抛出去,由頂層的調用方去處理,而不是 try/catch

  1. 在 catch 中調用 Thread.currentThread().interrupt(); 來恢複設定中斷狀态
public class RightWayStopThreadInProd2 implements Runnable {

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

    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("程式運作結束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}