wait/notify
Java在Object類中定義了一些線程協作的基本方法,wait和notify
public final void wait() throws InterruptedException;
public final native void wait(long timeout) throws InterruptedException;
一個帶時間參數,單位是毫秒,表示最多等待的時長,如果爲0則表示無限期等待;
一個不帶時間參數,表示無限期等待,實際就是調用wait(0)
在等待期間都可以被中斷,如果被中斷,會拋出InterruptedException異常
每個對象都有一把鎖和等待隊列,一個線程在進入synchronized代碼塊時,會嘗試獲取鎖,如果獲取不到則會吧當前線程加入等待隊列中。除了用於鎖的等待隊列,每個對象還有另外一個等待隊列,表示條件隊列,該隊列用於線程間的協作。
調用wait就會把當前線程放到條件隊列上並阻塞,表示當前線程執行不下去了,它需要等待一個條件,需要其他線程來改變。當其他線程改變了條件後,應該調用Object的notify方法,notify做的就是從條件隊列中選一個線程,將其從隊列中移除並喚醒
// 從條件隊列中選一個線程,移除並喚醒
public final native void notify();
// 移除條件隊列中所有的線程並喚醒
public final native void notifyAll();
這裏做一個簡單的示例,一個線程在啓動後,在執行某個操作前,需要等待主線程給它的指令,收到指令後才執行
public class WaitThread extends Thread {
private volatile boolean fire = false;
@Override
public void run() {
synchronized (this) {
try {
while (!fire) {
System.out.println("start wait");
wait();
System.out.println("wait end");
}
System.out.println("fire !!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void fire() throws InterruptedException {
this.fire = true;
System.out.println("notify");
notify();
Thread.sleep(5000);
System.out.println("notify !!!");
}
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();
Thread.sleep(1000);
waitThread.fire();
}
}
wait/notify 這方法只能在synchronized代碼塊內被調用
可能會有疑問,兩個方法都在synchronized代碼塊內,一個線程在wait時,另一個線程怎麼可以調用到notify呢?
實際上調用wait時,線程會釋放對象鎖
只有在把包含notify的synchronized代碼塊執行完以後,等待的線程纔會從wait調用中返回。
協作場景
生產者/消費者協作模式:生產者線程和消費者線程通過共享隊列進行協作,生產者將數據或任務放到隊列上,而消費者從隊列上取數據或任務。如果隊列長度有限,在隊列滿的時候,生產者需要等待,而在隊列爲空的時候,消費者需要等待。
Java提供了專門的阻塞隊列實現:- 接口BlockingQueue和BlockingDeque
- 基於數組的實現類ArrayBlockingQueue
- 基於鏈表的實現類LinkedBlockingQueue和LinkedBlockingDeque
- 基於堆的實現類PriorityBlockingQueue
同時開始: 要求多個線程能同時開始工作
- 等待結束:主線程將任務分解爲若干子任務,爲每個子任務創建一個線程,主線程在繼續執行其他任務之前需要等待每個子任務執行完畢
- 異步結果
- 集合點: 比如在並行迭代計算中,每個線程負責一部分計算,然後在集合點等待其他線程完成,所有線程到齊後,交換數據和結果,再進行下一次迭代。
線程的中斷
在Java中,停止一個線程的主要機制是中斷,中斷並不是強迫終止一個線程,它是一種協作機制,是給線程傳遞一個取消信號,但是由線程決定如何以及何時退出
// 返回對應線程的中斷標誌位是否爲true
public boolean isInterrupted();
// 中斷對應的線程
public void interrupte();
// 返回當前線程的中斷標誌位是否爲true,同時清空中斷標誌位
public static boolean interrupted();
interrupted()對線程的影響與線程的狀態和在進行的IO操作有關。我們主要考慮線程的狀態,IO操作的影響和具體IO以及操作系統有關
RUNNABLE
如線程再運行或具備運行條件只是在等待操作系統調度,且沒有執行IO操作,interrupted()只是會設置線程的中斷標誌位,沒有任何其他作用。
不過我們可以在合適的位置檢查中斷標誌位
WAITING/TIMED_WAITING
線程調用join/wait/sleep方法會進入WAITING/TIMED_WAITING狀態,此時調用interrupt()會使得線程拋出InterruptedException。拋出異常後,中斷標誌位會被清空
BLOCKED
如果線程在等待鎖,對線程對象調用interrupted()只是會設置線程的中斷標誌位,線程依然會處於BLOCKED狀態
NEW/TERMINATED
如果線程尚未啓動(NEW),或者已經結束(TERMINATED),調用interrupted()對它沒有任何效果。