問題
- 爲什麼LockSupport 是核心基礎類?
- 如果在wait()之前執行了notify()會怎樣?
- 如果在park()之前執行了unpark()會怎樣?
- 寫出分別通過wait/notify和LockSupport的park/unpark實現同步?
- LockSupport.part() 與 Object.await()、Condition.await() 的區別是啥?
初始化
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
// hread 中 parkBlocker 屬性的內存偏移地址
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
// Thread 中 threadLocalRandomSeed 屬性的內存偏移地址
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
// Thread 中 threadLocalRandomProbe 屬性的內存偏移地址
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
// Thread 中 threadLocalRandomSecondarySeed 屬性的內存偏移地址
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
// 私有構造函數,不能被實例化。
private LockSupport() {}
核心操作
當前線程被阻塞。
被喚醒的條件
- 其他線程調用 unpark
- 其他線程中斷當前線程
- 超時
- 虛假喚醒
對於有條件的阻塞,在喚醒後必須立即重新檢查之前的條件是否成立。建議使用 condition
// 如果線程在 park 上受阻塞,將解除其阻塞狀態。
// 如果線程沒有在 park 上受阻塞,線程下次調用 park 將不會阻塞。
// 如果給定線程尚未啓動,則無法保證此操作有任何效果。
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
// 一直阻塞
public static void park() {
UNSAFE.park(false, 0L);
}
// 從現在開始阻塞 nanos 納秒
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
// 從現在開始阻塞 nanos 納秒
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
// 一直阻塞
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
// 從現在開始阻塞 nanos 納秒
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
// 喚醒後設置線程的 parkBlocker 爲 null
setBlocker(t, null);
}
}
// 阻塞到 deadline
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
注:parkBlocker 在 Thread 中沒有實際意義,僅僅是用於向當前線程保存自定義的信息(比如調試、監控)。在調用 park 的時候,需要設置某些信息,其他線程可以通過 getBlocker 獲取到設置的信息。在線程處於阻塞的時候,可以通過 getBlocker 拿到設置的信息。因此,理解 park/unpark 相關操作,可以忽略 blocker。
底層原理
park()/unpark()底層的原理是“二元信號量”,假設有個計數器 counter 初始爲 0
park
- 如果 counter 爲 1,將 counter 設置爲 0,並返回;
- 如果 counter 爲 0,等待,直到其他線程喚醒,將 counter 設置爲 0,並返回。
unpark:
- 如果 counter 爲 0,將 counter 設置爲 1,並喚醒 park 線程,並返回;
- 如果 counter 爲 1,立即返回。
情景 1:
- 線程 A 調用 unpark 的時候,counter 設置 1,並返回。
- 線程 A 繼續多次調用 unpark,由於 counter 已經爲 1,直接返回
- 線程 B 調用 park,將 counter 設置爲 0,直接返回。
情景2:
- 線程 A 調用 park,此時,counter 爲 0,阻塞
- 線程 B 調用 unpark,將 counter 設置爲 1,並喚醒線程 A。
- 線程 A 從阻塞中恢復,將 counter 設置爲 0,並返回;
仔細理解了 park 和 unpark 的底層是二元信號量的本質,那麼,不關 park 和 unpark 怎麼變化,都不會出錯。
總結
LockSupport 本質就是二元信號量,理解了這個本質,LockSupport 就非常簡單了。
附錄
各種阻塞對比
比較 | LockSupport.park() | Object.wait() | condation.await() | Thread.sleep() |
---|---|---|---|---|
是否釋放鎖 | 不釋放 | 釋放鎖 | 釋放鎖 | 不釋放鎖 |
指定等待時間 | 可選 | 可選 | 可選 | 必須 |
喚醒方式 | unpark或超時 | notify或超時 | notify 或超時 | 自動 |
底層原理 | UNSAFE.park | native | UNSAFE.park | native |
其他:
-
如果在wait()之前執行了notify()會怎樣? 拋出IllegalMonitorStateException異常;
-
如果在park()之前執行了unpark()會怎樣? 線程不會被阻塞,直接跳過park(),繼續執行後續內容;
-
使用wait/notify實現同步時,必須先調用wait,後調用notify,如果先調用notify,再調用wait,將起不了作用。
使用 park/unpark 實現同步時,park 和 unpark 沒有順序要求。
- 線程 A,線程 B 啓動,線程 A 先調用 park,線程 B 後調用 unpark,線程 A 在線程 B 調用 unpark 後解除阻塞。
- 線程 A,線程 B 啓動,線程 A 調用 unpark,線程 B 先調用 park,線程 B 後調用 park 之後不會阻塞,因爲 線程 A 已經調用了 unpark。