多執行緒核心知識總結(三)——執行緒停止,中斷總結

2020-10-02 12:00:01

多執行緒核心知識總結

三.執行緒停止,中斷

1.講解原理

原理介紹:使用interrupt了來通知,而不是強制
使用一個執行緒來通知另一個執行緒該停止的機制,只是一種通知,如果該執行緒本身不決定停止,則其不會停止,被停止執行緒的本身,更熟悉停止自己需要做那些處理和清理工作,所以正確停止執行緒,是如何使用interrupt合理通知該執行緒並讓該執行緒配合停止。

2.最佳實踐

通常執行緒會在什麼情況下停止

  • run方法的所有程式碼都執行完畢了
  • 有異常出現,並且方法中沒有捕獲

正確停止方法:interrupt

  • 通常情況下執行緒會在什麼情況下停止
/**
 * 描述:     run方法內沒有sleep或wait方法時,停止執行緒
 */
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 + "是10000的倍數");
            }
            num++;
        }
        System.out.println("任務執行結束了");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}
  • 執行緒可能被阻塞
/**
 * 描述:     帶有sleep的中斷執行緒的寫法
 */
public class RightWayStopThreadWithSleep {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍數");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}
  • 如果執行緒在每次迭代後都阻塞
/**
 * 描述:     如果在執行過程中,每次迴圈都會呼叫sleep或wait等方法,那麼不需要每次迭代都檢查是否已中斷
 */
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++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

自動清除中斷訊號

/**
 * 描述:     如果while裡面放try/catch,會導致中斷失效
 */
public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

中斷執行緒的兩種姿勢之優先丟擲

/**
 * 描述:  catch了InterruptedExcetion之後的優先選擇:在方法簽名中丟擲異常 那麼在run()就會強制try/catch
 */
public class RightWayStopThreadInProd implements Runnable {

    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                //儲存紀錄檔、停止程式
                System.out.println("儲存紀錄檔");
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
            Thread.sleep(2000);
    }

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

中斷執行緒的兩種姿勢之恢復中斷

/**
 * 描述:在catch子語句中呼叫Thread.currentThread().interrupt()來恢復設定中斷狀態,以便於在後續的執行中,依然能夠檢查到剛才發生了中斷
 * 回到剛才RightWayStopThreadInProd補上中斷,讓它跳出
 */
public class RightWayStopThreadInProd2 implements Runnable {

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

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

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

總結

  • 優先選擇:傳遞中斷,使用throw丟擲異常到run方法內集中解決

  • 不想或者無法傳遞: 恢復中斷,在catch中,重新interrupt

  • 不應該遮蔽中斷

相應執行緒中斷的方法總結

  • Object.wait()/wait(long)/wait(long,int)
  • Thread.sleep(long)/sleep(long,int)
  • Thread.join()/join(long)/join(long,int)
  • java.util.concurrent.BlockingQueue.take()/put(E)
  • java.util…concurrent.locks.Lock.lockIntrruptibly()
  • java.util.concurrent.CountDownLatch.await()
  • java.util.concurrent.Exchanger.exchange(V)
  • java.nio.channels.Selector相關方法
  • java.nio.channels.InterruptibleChannel相關方法

正確停止執行緒

錯誤停止的方法

  • 被棄用的stop,suspend,resume方法
/**
 * 描述:     錯誤的停止方法:用stop()來停止執行緒,會導致執行緒執行一半突然停止,沒辦法完成一個基本單位的操作(一個連隊),會造成髒資料(有的連隊多領取少領取裝備)。
 */
public class StopThread implements Runnable {

    @Override
    public void run() {
        //模擬指揮軍隊:一共有5個連隊,每個連隊10人,以連隊為單位,發放武器彈藥,叫到號的士兵前去領取
        for (int i = 0; i < 5; i++) {
            System.out.println("連隊" + i + "開始領取武器");
            for (int j = 0; j < 10; j++) {
                System.out.println(j);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("連隊"+i+"已經領取完畢");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();
    }
}
  • 用volatile設定boolean標記位
/**
 * 描述:     演示用volatile的侷限:part1 看似可行
 */
public class WrongWayVolatile implements Runnable {

    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(5000);
        r.canceled = true;
    }
}

/**
 * 描述:演示用volatile的侷限part2 陷入阻塞時,volatile是無法執行緒的 此例中,生產者的生產速度很快,消費者消費速度慢,所以阻塞佇列滿了以後,生產者會阻塞,等待消費者進一步消費
 */
public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take()+"被消費了");
            Thread.sleep(100);
        }
        System.out.println("消費者不需要更多資料了。");

        //一旦消費不需要更多資料了,我們應該讓生產者也停下來,但是實際情況
        producer.canceled=true;
        System.out.println(producer.canceled);
    }
}

class Producer implements Runnable {

    public volatile boolean canceled = false;

    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }


    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍數,被放到倉庫中了。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生產者結束執行");
        }
    }
}

class Consumer {

    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}
/**
 * 描述:     用中斷來修復剛才的無盡等待問題
 */
public class WrongWayVolatileFixed {

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = body.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消費了");
            Thread.sleep(100);
        }
        System.out.println("消費者不需要更多資料了。");


        producerThread.interrupt();
    }


    class Producer implements Runnable {

        BlockingQueue storage;

        public Producer(BlockingQueue storage) {
            this.storage = storage;
        }


        @Override
        public void run() {
            int num = 0;
            try {
                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        storage.put(num);
                        System.out.println(num + "是100的倍數,被放到倉庫中了。");
                    }
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生產者結束執行");
            }
        }
    }

    class Consumer {

        BlockingQueue storage;

        public Consumer(BlockingQueue storage) {
            this.storage = storage;
        }

        public boolean needMoreNums() {
            if (Math.random() > 0.95) {
                return false;
            }
            return true;
        }
    }
}

停止執行緒相關重要函數解析

  • 判斷是否已經被中斷的方法

    • static boolean interrupted()
      返回之後執行緒中斷狀態設為false,會清除中斷狀態
    • boolean isInterrupted()
      返回執行緒中斷狀態,但不清除中斷狀態
/**
 * 描述:     注意Thread.interrupted()方法的目標物件是「當前執行緒」,而不管本方法來自於哪個物件
 */
public class RightWayInterrupted {

    public static void main(String[] args) throws InterruptedException {

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                }
            }
        });

        // 啟動執行緒
        threadOne.start();
        //設定中斷標誌
        threadOne.interrupt();
        //獲取中斷標誌
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //獲取中斷標誌並重置
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //獲取中斷標誌並重直
        System.out.println("isInterrupted: " + Thread.interrupted());
        //獲取中斷標誌
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

最終結果為

isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true

常見面試問題

1.如何停止執行緒:
1.原理:用interrupt來請求,好處(保證資料安全,把中斷許可權交給被中斷執行緒)
2.想停止執行緒,要請求方,被停止方,子方法被呼叫方法相互配合
3.最後再說錯誤的方法:stop/suspend被廢棄,volatile的boolean無法處理長時間阻塞的狀態
2.如何處理不可中斷的阻塞
1.不存在通用的解決方案,針對特定情況,需要去找可以相應中斷的方法