停止多執行緒的正確方法
阿新 • • 發佈:2021-11-26
原理介紹:
使用interrupt來通知,而不是強制
最佳實踐: 如何正確停止執行緒
- 通常的停止過程(無外界干涉的情況下)
- run()方法執行完畢
- 有一點異常出現,但沒有被捕獲
- 正確方法: 用interrupt來請求停止執行緒
- 普通情況(run方法內沒有sleep或wait方法的標準寫法)
- 執行緒可能被阻塞
- 如果執行緒在每次工作迭代後都阻塞(呼叫sleep方法等)
- 如果不這樣寫,會遇到的問題: 執行緒無法停止
- 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(); }
- 實際生產開發時要注意的編碼習慣
- 兩種最佳處理方式
- 優先選擇: 傳遞中斷,直接throw exception
- 不想或無法傳遞: 恢復中斷, 通過
Thread.currentThread().interrupt();
- 不應該遮蔽中斷
- 兩種最佳處理方式
- 可以為了響應中斷而丟擲interruptedException的常見方法列表總結
- 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.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.concurrent.Exchanger.exchange(V)
- java.util.concurrent.CyclicBarrier.await()
- java.nio.channels.InterruptibleChannel相關方法
- java.nio.channels.Selector的相關方法
錯誤的停止方法
- 被棄用的stop、suspend和resume方法
- 使用stop的後果
- 用stop()來停止執行緒,會導致執行緒執行一半突然停止,沒辦法完成一個基本單位的操作(一個連隊), 會造成髒資料(有的連隊會多領取少領取裝備)
- 關於stop的錯誤理論
- 使用stop不能釋放鎖,這是錯誤的
- suspend的問題
- suspend使執行緒暫停,但是不會釋放類似鎖這樣的資源,時間久了會造成死鎖
- resume: 使執行緒恢復,如果之前沒有使用suspend暫停執行緒,則不起作用。
- 使用stop的後果
- 用volatile設定Boolean標記位
- 看上去可行
- 錯誤之處
- 阻塞住了,沒有去校驗
- 修正方式
- 通過interrupt方法解決,而不是volatile
- 演示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;
}
}
停止執行緒相關的重要函式解析
- 中斷執行緒
- interrupt方法原理分析
- 判斷是否已經被中斷
- static boolean interrupted()
- boolean isInterrupted()
- 舉例說明,注意Thread.interrupted()的目的物件是“當前執行緒”,
常見面試問題
- 如何停止一個執行緒
- 原理: 用interrupt來請求、好處
- 想停止執行緒,要請求方、被停止方、子方法被呼叫方互相配合
- 最後再說錯誤的方法: stop/suspend已廢棄,volatile的Boolean方法無法處理長時間阻塞的情況
- 如果處理不可中斷的阻塞(例如搶鎖時ReentrantLock.lock()或者Socket I/O時無法響應中斷,那應該怎麼讓執行緒停止呢?)