天天看點

面試官:如何停止一個正在運作的線程?我又懵了

本篇講講如何終止子線程

暴力停止——Stop方法

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 09:40:34
 */
public class Main1 {
    static class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保證子線程進入運作狀态,避免還沒運作就被終止
        Thread.sleep(100);
        //暴力停止子線程
        myThread.stop();
    }
}      

輸出如下:

面試官:如何停止一個正在運作的線程?我又懵了

可以看得出,線程确實被終止了。

但是,這個是一個被廢棄掉的方法,Stop方法上的注釋如下:

面試官:如何停止一個正在運作的線程?我又懵了

從上面的注釋,我們可以得到以下的資訊:

  1. Stop方法時被廢棄掉的方法,不推薦使用
  2. 使用Stop方法,會一直向上傳播ThreadDeath異常,進而使得目标線程解鎖所有鎖住的螢幕,即釋放掉所有的對象鎖。使得之前被鎖住的對象得不到同步的處理,是以可能會造成資料不一緻的問題。
  3. 注釋中建議我們取代Stop方法,可以增加一個變量。目标線程周期性地檢查這個變量。如果變量在某個時間訓示線程終止,則目标線程将以有序的方式從run方法中傳回。
  4. 當然,如果目标線程長時間進行等待,則可以使用中斷的方式來終止線程。

是以,我們打算在下面的方法中,使用變量或中斷的方式來終止線程。

周期性檢查條件變量

通過指定一個條件變量,外部線程(比如是主線程)可以控制該變量,内部線程(比如子線程)在内部循環檢查該變量。為了保證條件變量在記憶體中的可見性,我們使用volatile修飾它。有關volatile是如何保證可見性的,可以參考我的另外一篇文章​​多線程之記憶體可見性​​

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 10:30:30
 */
public class Main2 {
    static class MyThread extends Thread {
        //條件變量
        private volatile boolean stop = false;
        private int i = 0;

        public void finish() {
            stop = true;
        }

        @Override
        public void run() {
            //循環檢查條件變量
            while (!stop) {
                //業務代碼
                i++;
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保證子線程進入運作狀态,避免還沒運作就被終止
        Thread.sleep(100);
        //修改條件變量為false
        myThread.finish();
    }
}      

可以看得出,如果我們的業務要求在每一次循環結束後去檢查條件變量,那麼以上的寫法是沒有問題的。

但是,以上方式的即時性不高,如果我們的業務代碼有等待操作,比如sleep與wait操作,那麼如何在這些操作中就終止線程呢?

條件變量結合中斷

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 11:51:30
 */
public class Main3 {
    static class MyThread extends Thread {
        //條件變量
        private volatile boolean stop = false;
        private int i = 0;

        public void finish() {
            stop = true;
            this.interrupt();
        }

        @Override
        public void run() {
            //循環檢查條件變量
            while (!stop) {
                //業務代碼
                i++;
                System.out.println(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("線程在sleep期間被打斷了");
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保證子線程進入運作狀态,避免還沒運作就被終止
        Thread.sleep(1000);
        //停止子線程
        myThread.finish();
    }
}      

輸出:

面試官:如何停止一個正在運作的線程?我又懵了

interrupt方法用來設定線程的中斷狀态,如果目标線程正阻塞于wait、sleep等方法時,首先會清除目前線程的中斷狀态,然後抛出java.lang.InterruptedException異常。

可以看得出,子線程确實被終止掉了。

能不能使用Thread.isInterrupted()來代替條件變量呢?

使用Thread.isInterrupted()來代替條件變量

我們将代碼稍微改造一下,使用Thread.isInterrupted()用來擷取目标線程是否處于中斷狀态。

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 14:03:00
 */
public class Main4 {
    static class MyThread extends Thread {
        private int i = 0;

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                //業務代碼
                i++;
                System.out.println(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("線程在sleep期間被打斷了");
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保證子線程進入運作狀态,避免還沒運作就被終止
        Thread.sleep(1000);
        //停止子線程
        myThread.interrupt();
    }
}      

觀察輸出:

面試官:如何停止一個正在運作的線程?我又懵了

什麼情況,打斷了為什麼還能繼續輸出?

在上面其實已經說過了,interrupt方法用來設定線程的中斷狀态,如果目标線程正阻塞于wait、sleep等方法時,首先會清除目前線程的中斷狀态,然後抛出java.lang.InterruptedException異常。

目标線程正在進行第11次循環,進入了sleep操作中,此時收到了主線程的interrupt操作,目标線程擁有了中斷狀态,則先清除中斷狀态,然後抛出異常,下一次循環檢測Thread.currentThread().isInterrupted()時,isInterrupted依然傳回false,進而繼續進行循環操作。

那怎麼停止目前線程呢,很簡單,處理異常時,再次打斷即可,代碼如下:

package com.qcy.testStopThread;

/**
 * @author qcy
 * @create 2020/09/16 14:03:00
 */
public class Main4 {
    static class MyThread extends Thread {
        private int i = 0;

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                //業務代碼
                i++;
                System.out.println(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("線程在sleep期間被打斷了");
                    e.printStackTrace();
                    //再次打斷,設定中斷标志,則之後的isInterrupted為true
                    this.interrupt();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        //保證子線程進入運作狀态,避免還沒運作就被終止
        Thread.sleep(1000);
        //停止子線程
        myThread.interrupt();
    }
}      

輸出如下:

總結

繼續閱讀