5 分鐘掌握 JDK 源碼之 LockSupport

問題

  • 爲什麼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() {}

核心操作

當前線程被阻塞。

被喚醒的條件

  1. 其他線程調用 unpark
  2. 其他線程中斷當前線程
  3. 超時
  4. 虛假喚醒

對於有條件的阻塞,在喚醒後必須立即重新檢查之前的條件是否成立。建議使用 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

  1. 如果 counter 爲 1,將 counter 設置爲 0,並返回;
  2. 如果 counter 爲 0,等待,直到其他線程喚醒,將 counter 設置爲 0,並返回。

unpark:

  1. 如果 counter 爲 0,將 counter 設置爲 1,並喚醒 park 線程,並返回;
  2. 如果 counter 爲 1,立即返回。

情景 1:

  1. 線程 A 調用 unpark 的時候,counter 設置 1,並返回。
  2. 線程 A 繼續多次調用 unpark,由於 counter 已經爲 1,直接返回
  3. 線程 B 調用 park,將 counter 設置爲 0,直接返回。

情景2:

  1. 線程 A 調用 park,此時,counter 爲 0,阻塞
  2. 線程 B 調用 unpark,將 counter 設置爲 1,並喚醒線程 A。
  3. 線程 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

其他:

  1. 如果在wait()之前執行了notify()會怎樣? 拋出IllegalMonitorStateException異常;

  2. 如果在park()之前執行了unpark()會怎樣? 線程不會被阻塞,直接跳過park(),繼續執行後續內容;

  3. 使用wait/notify實現同步時,必須先調用wait,後調用notify,如果先調用notify,再調用wait,將起不了作用。

使用 park/unpark 實現同步時,park 和 unpark 沒有順序要求。

  1. 線程 A,線程 B 啓動,線程 A 先調用 park,線程 B 後調用 unpark,線程 A 在線程 B 調用 unpark 後解除阻塞。
  2. 線程 A,線程 B 啓動,線程 A 調用 unpark,線程 B 先調用 park,線程 B 後調用 park 之後不會阻塞,因爲 線程 A 已經調用了 unpark。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章