[java - LockSupport sleep wait ]
沒問題:LockSupport、sleep和wait在多線程編程里作用不同,我一個一個給你講,然后再對比著說,這樣能講得更透徹。
1. LockSupport
LockSupport是Java并發包java.util.concurrent.locks里的一個類,主要用于線程的阻塞和喚醒。它提供了park()和unpark()方法。
park():這個方法會阻塞當前線程,除非有以下幾種情況發生才會被喚醒:- 另一個線程調用了當前線程的
unpark()方法。 - 線程被中斷。
- 發生了虛假喚醒(雖然概率極低,但在編寫代碼時要考慮這種情況)。
- 另一個線程調用了當前線程的
unpark(Thread thread):喚醒指定的線程。如果線程thread正在被park()阻塞,那么它會立即被喚醒繼續執行;如果該線程還沒調用park(),那么下一次調用park()時會直接跳過阻塞。
2. sleep
sleep是Thread類的一個靜態方法,作用是讓當前線程暫停執行一段指定的時間。
Thread.sleep(long millis):線程會暫停執行指定的毫秒數。在這段時間內,線程不會占用CPU資源,會進入睡眠狀態。Thread.sleep(long millis, int nanos):這個方法可以更精確地指定暫停時間,精確到納秒。不過由于操作系統的調度等原因,實際的暫停時間可能會略有偏差。
需要注意的是,sleep方法不會釋放鎖。如果線程在持有鎖的情況下調用sleep,其他線程仍然無法獲取該鎖,直到線程從睡眠中醒來并繼續執行,釋放鎖后其他線程才有機會獲取。
3. wait
wait是Object類的一個方法,用于讓線程在某個條件不滿足時等待,直到被其他線程喚醒。
wait():會使當前線程進入等待狀態,同時釋放對象的鎖。這一點和sleep有很大區別,釋放鎖后其他線程就可以獲取該鎖并執行相關操作。wait(long timeout):線程會等待指定的時間,或者直到被其他線程喚醒。如果在指定時間內沒有被喚醒,線程會自動醒來繼續執行。wait(long timeout, int nanos):同樣是更精確地指定等待時間,精確到納秒。
調用wait方法必須在synchronized代碼塊中,否則會拋出IllegalMonitorStateException異常。
4. 對比總結
- 鎖的處理:
LockSupport不涉及鎖的釋放和獲取,它主要是控制線程的阻塞和喚醒,與鎖機制相對獨立。sleep不會釋放鎖,持有鎖時調用sleep可能會導致死鎖,因為其他線程無法獲取該鎖。wait會釋放對象鎖,使得其他線程有機會獲取鎖執行操作,這是它和sleep的重要區別。
- 喚醒方式:
LockSupport通過unpark()方法喚醒指定線程,靈活性較高,而且一個線程可以多次調用`
我的理解: LockSupport 最好
sleep不釋放鎖,wait 之前 收到 notify 然后wait 就會一直 wait ,只有 LockSupport 支持 之前收到unpark信號 ,然后 park 就直接跳過,信號不會丟失
LockSupport 是 Java 并發包里的一個底層工具類,核心功能是“阻塞/喚醒線程”,但它的設計和 Thread.sleep()、Object.wait() 有本質區別。咱們從 使用場景、底層原理、核心差異 三個維度徹底講透:
一、先明確三者的核心功能
Thread.sleep(long):讓當前線程暫停執行指定時間,時間到后自動喚醒。Object.wait():讓當前線程阻塞,等待其他線程調用同一個對象的notify()/notifyAll()喚醒。LockSupport.park():讓當前線程阻塞,等待其他線程調用LockSupport.unpark(Thread)喚醒(指定線程喚醒)。
二、核心差異對比(用表格一目了然)
| 對比項 | Thread.sleep(long) |
Object.wait() |
LockSupport.park() |
|---|---|---|---|
| 是否需要鎖 | 不需要(不涉及鎖) | 必須在 synchronized 塊中調用(持有對象鎖) |
不需要(無鎖要求) |
| 喚醒方式 | 時間到自動喚醒 | 其他線程調用 notify()/notifyAll() |
其他線程調用 unpark(目標線程) |
| 喚醒的針對性 | 無(只能自己醒) | 隨機喚醒一個或全部等待線程(無法指定) | 精準喚醒指定線程(參數是目標線程) |
| 阻塞時是否釋放鎖 | 不釋放(本身不持有鎖) | 釋放持有的對象鎖 | 不釋放(本身不持有鎖) |
| “提前喚醒”處理 | 無法提前喚醒(只能等時間到) | 可能被虛假喚醒(需用 while 循環檢查) |
支持“先喚醒后阻塞”(不會丟信號) |
三、逐個拆解:為什么這些差異很重要?
1. Thread.sleep():最簡單的“定時休眠”
- 特點:不涉及鎖,調用后線程進入
TIMED_WAITING狀態,時間到自動恢復。 - 典型場景:簡單的延遲執行(比如每隔1秒打印一次日志)。
- 注意:
- 不會釋放任何鎖(因為它本來就不要求持有鎖)。
- 無法被“提前喚醒”(除非被中斷,
interrupt()會拋出InterruptedException)。
2. Object.wait():基于“對象鎖”的等待喚醒
- 特點:必須在
synchronized塊中調用(持有鎖),調用后釋放鎖,線程進入WAITING狀態,需被notify()喚醒。 - 典型場景:多線程協作(比如生產者-消費者模型,消費者等生產者喚醒)。
- 核心問題:
- 必須依賴鎖:如果忘了加
synchronized,直接拋IllegalMonitorStateException。 - 喚醒不精準:
notify()隨機喚醒一個等待線程,notifyAll()喚醒所有,無法指定喚醒某個線程,可能導致“驚群效應”(大量線程被喚醒后競爭鎖,大部分又阻塞)。 - 可能虛假喚醒:即使沒有被
notify(),也可能被JVM隨機喚醒,所以必須用while循環檢查條件(比如while (!條件) { wait(); })。
- 必須依賴鎖:如果忘了加
3. LockSupport.park():更靈活的“無鎖阻塞喚醒”
LockSupport 是 Unsafe 類的封裝,底層直接操作線程的狀態,是 ReentrantLock、CountDownLatch 等并發工具的“底層發動機”。
-
核心優勢:
① 無鎖依賴:不需要在synchronized或鎖塊中調用,直接阻塞當前線程,更靈活。
② 精準喚醒:unpark(thread)可以指定喚醒某個線程,避免“驚群效應”。
③ 信號不丟失:支持“先喚醒,后阻塞”——如果先調用unpark(thread),再調用thread.park(),park()會直接返回(不會阻塞)。而wait()如果先調用notify()再wait(),會永遠阻塞(信號丟失)。
④ 可中斷:park()阻塞時,其他線程調用thread.interrupt()會讓它立即返回(不會拋異常,但可以通過Thread.interrupted()檢查中斷狀態)。 -
典型場景:實現高級并發工具(如
ReentrantLock的Condition等待/喚醒、ThreadLocal的回收機制等),或需要精準控制線程的場景。
四、用代碼示例看差異
示例1:wait() 的“信號丟失”問題
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
// 假設先執行了notify(),再執行wait()
System.out.println("t1等待");
lock.wait(); // 會永遠阻塞,因為信號已經丟了
System.out.println("t1被喚醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
// 主線程先調用notify()
synchronized (lock) {
lock.notify();
System.out.println("主線程喚醒");
}
結果:t1 會永遠阻塞,因為 notify() 在 wait() 之前執行,信號丟失。
示例2:LockSupport 解決“信號丟失”
Thread t1 = new Thread(() -> {
System.out.println("t1等待");
LockSupport.park(); // 阻塞(如果之前有unpark,這里直接通過)
System.out.println("t1被喚醒");
});
t1.start();
// 主線程先調用unpark()
LockSupport.unpark(t1); // 提前發送喚醒信號
System.out.println("主線程喚醒");
結果:t1 的 park() 會直接返回,打印“t1被喚醒”——信號不會丟失。
示例3:LockSupport 精準喚醒指定線程
Thread t1 = new Thread(() -> {
System.out.println("t1阻塞");
LockSupport.park();
System.out.println("t1被喚醒");
}, "t1");
Thread t2 = new Thread(() -> {
System.out.println("t2阻塞");
LockSupport.park();
System.out.println("t2被喚醒");
}, "t2");
t1.start();
t2.start();
// 精準喚醒t1(t2繼續阻塞)
LockSupport.unpark(t1);
結果:只有 t1 被喚醒,t2 保持阻塞——這是 wait() 做不到的(notify() 無法指定線程)。
五、總結:什么時候用哪種?
- 簡單定時延遲:用
Thread.sleep()(最簡單)。 - 基于對象鎖的多線程協作(且能接受隨機喚醒):用
Object.wait()+notify()(依賴synchronized)。 - 需要靈活控制(無鎖、精準喚醒、防信號丟失):用
LockSupport.park()+unpark()(底層工具,適合實現復雜并發邏輯)。
一句話:LockSupport 是更底層、更靈活的線程阻塞喚醒工具,擺脫了對 synchronized 鎖的依賴,解決了 wait() 的信號丟失和喚醒不精準問題,是Java并發工具的基礎。

浙公網安備 33010602011771號