?1.?線程饑餓
線程饑餓是指一個或多個線程因長期無法獲取所需資源(如鎖,CPU時間等)而持續處于等待狀態,導致其任務無法推進的現象。
典型場景
-
優先級搶占:
-
在支持線程優先級的系統中,高優先級線程可能持續搶占CPU資源
-
導致低優先級線程長期無法獲得CPU時間片
-
-
不公平鎖競爭:
-
某些線程頻繁獲取鎖,其他線程長期等待
-
典型場景:某個線程釋放鎖后立即重新競爭并獲得鎖
-
導致其他線程始終處于BLOCKED狀態而無法執行
-
-
資源分配不均:
-
某些線程占用大量I/O或內存資源
-
其他線程因資源不足而無法執行
-
-
線程池配置不當:
-
固定大小線程池中,長任務占用所有線程
-
短任務無法得到執行機會
-
關鍵問題點
-
鎖獲取模式問題:
-
活躍線程釋放鎖后立即重新請求鎖
-
處于RUNNABLE狀態的線程比BLOCKED狀態的線程有更快的響應速度
-
操作系統喚醒BLOCKED線程需要上下文切換,造成競爭劣勢
-
-
系統調度機制:
-
默認調度策略可能不利于公平性
-
缺乏有效的防饑餓機制
-
-
線程狀態轉換開銷:
-
BLOCKED→RUNNABLE狀態轉換需要系統介入
-
這種轉換比保持RUNNABLE狀態有更高的延遲
-
2.?wait 和 notify
wait/notify 的本質作用
- 應用層協作工具:
wait()
?和?notify()
?是 Java 提供的應用層線程協作機制,用于控制線程對共享資源的訪問順序,而非直接干預操作系統的線程調度策略。 - 不改變調度規則:操作系統內核仍按自身調度算法(如輪轉法、優先級調度)決定線程何時獲得 CPU 時間,
wait/notify
?無法強制指定某個線程優先執行。
1.?wait()?法
wait(); 內部做的三件事:
1.?立即釋放鎖,無需等待同步塊結束
2. 線程狀態變化:
RUNNING
?→?WAITING
3.?線程被喚醒后需重新獲取鎖,獲取成功后從?
wait()
?調用處繼續執行。WAITING
→?BLOCKED
(被喚醒后重新競爭鎖)→?RUNNING
。
線程狀態變化后,其他線程就有機會獲取鎖。
wait() 方法的三種重載形式
方法 | 說明 |
---|---|
wait() | 使當前線程無限期等待,直到另一個線程調用?notify()?或?notifyAll()?方法 |
wait(long timeout) | 指定一個超時時間,線程將在超時后自動被喚醒。線程也可以在超時前被?notify()?或?notifyAll()?方法喚醒。 |
wait(long timeout, int nanos) | 提供更高精度的超時設置,總超時時間(以納秒為單位)計算為?1_000_000*timeout + nanos |
在 Java 中,調用?wait
?方法的對象必須和鎖對象一致,這是因為?wait
?方法的行為是基于對象的監視器(鎖)來實現的。以下是具體解釋:
- 原理:當一個線程調用某個對象的?
wait
?方法時,該線程會釋放它所持有的該對象的鎖,并進入等待狀態,直到其他線程調用同一個對象的?notify
?或?notifyAll
?方法來喚醒它。如果調用?wait
?方法的對象與獲取鎖的對象不一致,那么線程在等待時就無法正確地與該鎖關聯,也就無法按照預期被喚醒,并且可能會導致程序出現邏輯錯誤。
2.?notify()?法
方法 | 說明 |
---|---|
notify() | 喚醒等待該對象監視器的一個隨機線程。選擇喚醒哪個線程是非確定性的,取決于“隨機調度”算法 |
notifyAll() | 喚醒所有等待該對象監視器的線程。被喚醒的線程會和其他試圖獲取該對象鎖的線程一起競爭鎖 |
調用?
notify()
?或?notifyAll()
?的對象必須與調用?wait()
?的對象相同,并且它們必須與?synchronized
?使用的鎖對象一致,否則會拋出?IllegalMonitorStateException
。
wait()
、notify()
、notifyAll()
?必須由同一個對象調用。必須在?
synchronized
?塊中使用,并且鎖對象必須與調用?wait()
/notify()
?的對象一致。每個 Java 對象都有一個監視器(monitor),也可以理解為鎖。當一個線程進入
synchronized
代碼塊時,它會獲取該代碼塊所關聯對象的鎖。wait
方法會讓當前線程釋放這個鎖,并進入等待狀態,直到其他線程調用同一個對象的notify
或notifyAll
方法來喚醒它。而notify
和notifyAll
方法也需要在獲取相同對象的鎖之后才能調用,這樣它們才能準確地喚醒在該對象上等待的線程。如果這三個對象不一致,就會破壞這種線程同步機制,導致程序出現不可預測的結果,例如線程無法被喚醒、死鎖等問題。
wait()
、notify()
?和?notifyAll()
?方法必須在?synchronized
?修飾的代碼塊或方法中調用,否則會拋出?IllegalMonitorStateException
。1.?鎖與等待隊列的綁定
每個 Java 對象都有兩個核心屬性:
- 監視器鎖(Monitor):用于實現同步。
- 等待隊列(Wait Set):用于存儲調用?
wait()
?的線程。
wait()
?和?notify()
?的操作對象是對象的等待隊列,而等待隊列的狀態由鎖來保護。因此,必須先獲取鎖才能操作等待隊列。2.?原子性與可見性保障
public class SynchronizedDomo8 {public static void main(String[] args) throws InterruptedException {Object object = new Object();// 作為同步鎖和 wait/notify 的監視器對象Thread t1 = new Thread(()->{synchronized (object){// 獲取 object 的鎖System.out.println("t1 線程之前");// ①try {//必須使用同一個對象調用object.wait();// ② 釋放鎖,進入WAITING狀態} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1 線程之后");// ⑦ 被喚醒后重新獲取鎖,繼續執行}});Thread t2 = new Thread(()->{try {Thread.sleep(2000);// ③ 休眠 2 秒,確保 t1 先執行并進入 wait()} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (object){// 獲取 object 的鎖System.out.println("t2 線程之前");// ④//必須使用同一個對象調用object.notify(); // ⑤ 喚醒t1,但t2仍持有鎖System.out.println("t2 線程之后");// ⑥ 同步塊結束后釋放鎖}});t1.start();t2.start();}
}
執行步驟:
-
t1
?進入?synchronized
?塊,獲取?object
?的鎖。 -
打印?
"t1 線程之前"
。 -
調用?
object.wait()
:-
釋放?
object
?的鎖,t1
?進入等待狀態。
-
-
t2
?先休眠 2 秒,確保?t1
?先執行并進入?wait()
?狀態。 -
t2
?進入?synchronized
?塊,獲取?object
?的鎖。 -
打印?
"t2 線程之前"
。 -
調用?
object.notify()
:-
喚醒?
t1(WAITING -> BLOCKED)
,但?t1
?不會立即執行,因為?t2
?仍持有鎖。
-
-
打印?
"t2 線程之后"
,退出?synchronized
?塊,釋放鎖。 -
t1
?重新獲取鎖,繼續執行?"t1 線程之后"
。
3.?wait()、join()、sleep()方法的區別
方法 | sleep() | join() | wait() |
---|---|---|---|
所屬類 | Thread類 | Thread類 | ?Object類? |
釋放鎖 | ? | ? | ? |
喚醒條件 | ?時間到期 | 目標線程結束或超時 | notify()/notifyAll()或超時 |
使用限制 | ?可以直接調用 | 可以直接調用 | 必須在同步塊中使用? |
?拋出InterruptedException | ? | ? | ? |
精度控制 | 毫秒(實際精度依賴操作系統) | ?毫秒+納秒? | ?毫秒+納秒? |
線程狀態變化 | RUNNING → WAITING | RUNNING → TIMED_WAITING | RUNNING → WAITING/TIMED_WAITING |