1. 程式人生 > 其它 >停止多執行緒的正確方法

停止多執行緒的正確方法

原理介紹:

使用interrupt來通知,而不是強制

最佳實踐: 如何正確停止執行緒

  1. 通常的停止過程(無外界干涉的情況下)
    1. run()方法執行完畢
    2. 有一點異常出現,但沒有被捕獲
  2. 正確方法: 用interrupt來請求停止執行緒
    1. 普通情況(run方法內沒有sleep或wait方法的標準寫法)
    2. 執行緒可能被阻塞
    3. 如果執行緒在每次工作迭代後都阻塞(呼叫sleep方法等)
    4. 如果不這樣寫,會遇到的問題: 執行緒無法停止
      1. while內try/catch的問題: java語言在設計sleep函式的時候的理念就是一旦中斷,就是清除interrupt標記位
public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                try {
                    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();
    }
  1. 實際生產開發時要注意的編碼習慣
    1. 兩種最佳處理方式
      1. 優先選擇: 傳遞中斷,直接throw exception
      2. 不想或無法傳遞: 恢復中斷, 通過Thread.currentThread().interrupt();
      3. 不應該遮蔽中斷
  2. 可以為了響應中斷而丟擲interruptedException的常見方法列表總結
    1. Object.wait()/wait(long)/wait(long,int)
    2. Thread.sleep(long)/sleep(long,int)
    3. Thread.join()/join(long)/join(long,int)
    4. java.util.concurrent.BlockingQueue.take()/put(E)
    5. java.util.concurrent.locks.Lock.lockInterruptibly()
    6. java.util.concurrent.CountDownLatch.await()
    7. java.util.concurrent.Exchanger.exchange(V)
    8. java.util.concurrent.CyclicBarrier.await()
    9. java.nio.channels.InterruptibleChannel相關方法
    10. java.nio.channels.Selector的相關方法

錯誤的停止方法

  1. 被棄用的stop、suspend和resume方法
    1. 使用stop的後果
      • 用stop()來停止執行緒,會導致執行緒執行一半突然停止,沒辦法完成一個基本單位的操作(一個連隊), 會造成髒資料(有的連隊會多領取少領取裝備)
    2. 關於stop的錯誤理論
      • 使用stop不能釋放鎖,這是錯誤的
    3. suspend的問題
      • suspend使執行緒暫停,但是不會釋放類似鎖這樣的資源,時間久了會造成死鎖
    4. resume: 使執行緒恢復,如果之前沒有使用suspend暫停執行緒,則不起作用。
  2. 用volatile設定Boolean標記位
    • 看上去可行
    • 錯誤之處
      • 阻塞住了,沒有去校驗
    • 修正方式
      • 通過interrupt方法解決,而不是volatile
  3. 演示volatile失效方式
/**
 * 演示用volatile的侷限 part2: 陷入阻塞時,volatile是無法停止執行緒的
 * 此例中,生產者的生產速度很快,消費者消費速度慢
 * 所以阻塞佇列滿了以後,生產者會阻塞,等待消費者進一步消費
 */
public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Object> storage = new ArrayBlockingQueue<>(10);
        Producer producer = new Producer(storage);
        Thread thread = new Thread(producer);
        thread.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;

    }
}


class Producer implements Runnable {

    BlockingQueue storage;

    public volatile boolean canceled = false;

    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() {
        return Math.random() <= 0.95;
    }
}

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

  1. 中斷執行緒
    1. interrupt方法原理分析
  2. 判斷是否已經被中斷
    1. static boolean interrupted()
    2. boolean isInterrupted()
    3. 舉例說明,注意Thread.interrupted()的目的物件是“當前執行緒”,

常見面試問題

  1. 如何停止一個執行緒
    1. 原理: 用interrupt來請求、好處
    2. 想停止執行緒,要請求方、被停止方、子方法被呼叫方互相配合
    3. 最後再說錯誤的方法: stop/suspend已廢棄,volatile的Boolean方法無法處理長時間阻塞的情況
  2. 如果處理不可中斷的阻塞(例如搶鎖時ReentrantLock.lock()或者Socket I/O時無法響應中斷,那應該怎麼讓執行緒停止呢?)