Java JUC LockSupport概述

神祕傑克發表於2022-01-19

LockSupport 工具類

LockSupport 是 rt.jar 包中的一個工具類,它的主要作用就是掛起喚醒執行緒,並且該類是建立鎖和其它同步類的基礎

LockSupport 類和每個使用它的執行緒都會關聯一個許可證,在預設情況下呼叫 LockSupport 類的方法是不持有許可證的。LockSupport 是使用 Unsafe 類實現的。

下面介紹一下 LockSupport 中的主要方法。

void park()

如果在呼叫 park()方法之前已經獲得到關聯的許可證,則呼叫後會立即返回,否則會被禁止參與執行緒的排程,也就是被阻塞掛起。

如下程式碼,最終只會輸出“開始”,然後當前執行緒被掛起,因為在預設情況下呼叫執行緒是不持有許可證的。

public static void main(String[] args) throws Exception {
        System.out.println("開始...");
        LockSupport.park();
        System.out.println("結束...");
}
//結果:
//開始...

在其它執行緒呼叫 unpark(Thread thread)方法並且將當前阻塞執行緒作為引數時,呼叫 park()方法阻塞的執行緒會被返回。當其它執行緒呼叫了阻塞執行緒的 interrupt()方法,設定中斷標誌或者執行緒被虛假喚醒,則阻塞執行緒也會被返回。

需要注意:

  1. 當呼叫 park()方法被阻塞的執行緒被其它執行緒中斷而返回時候,並不會丟擲 InterruptedException 異常。
  2. 許可證是一次性的。比如執行緒 B 連續呼叫了三次 unpark()方法,當執行緒 A 呼叫 park()方法就使用掉這個許可證,如果執行緒 A 再次呼叫 park()方法,則進入等待狀態。

void unpark(Thread t)

當一個執行緒呼叫 unpark()方法時,如果傳入的引數 thread 沒有持有許可證,則讓傳入的 thread 持有許可證;如果 thread 之前呼叫 park()後被掛起,則呼叫 unpark()時會喚醒該 thread;如果 thread 之前沒有呼叫 park(),則呼叫 unpark()方法後,再呼叫 park()方法時會立即返回。

public static void main(String[] args) throws Exception {
        System.out.println("開始...");
        LockSupport.unpark(Thread.currentThread());
        LockSupport.park();
        System.out.println("結束...");
}
//輸出結果:
//開始...
//結束...

看如下程式碼,加深對 park 和 unpark 的理解。

public static void main(String[] args) throws Exception {
        Thread t = new Thread(() -> {
            System.out.println("子執行緒開始...");
            //掛起自己
            LockSupport.park();
            System.out.println("子執行緒結束...");
        });
        t.start();
        Thread.sleep(1000);
        System.out.println("主執行緒開始呼叫unpark");
        LockSupport.unpark(t);
}
//輸出結果:
//子執行緒開始...
//主執行緒開始呼叫unpark
//子執行緒結束...

該段程式碼首先建立了子執行緒 t,子執行緒啟動後呼叫 park()方法。由於預設情況下子執行緒沒有持有許可證,則掛起自己。

主執行緒休眠 1 秒後,呼叫 unpark()並傳入子執行緒 t,這樣做就是為了讓子執行緒獲取到許可證,之後子執行緒中阻塞的 park()就會返回。

void parkNanos(long nanos)

和 park()方法類似,如果已經獲取到了許可證則呼叫該方法後會馬上返回。不同的是,如果沒有拿到許可證,則呼叫執行緒會被掛起 nanos 時間後自動返回。

另外 park()方法還支援帶有 blocker 引數的方法 void park(Object blocker)方法,當執行緒在沒有持有許可證的情況下呼叫 park()方法而被阻塞掛起時,這個 blocker 物件會被記錄到該執行緒內部。一般用於用於標識阻塞物件,該物件主要用於問題排查和系統監控

void parkNanos(Object blocker,long nanos) 該方法相對於上個方法,增加了超時時間。

void parkUntil(Object blocker,long deadline)

public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
}

引數 deadline 的時間單位為 ms,這個方法和 parkNanos(Object blocker,long nanos)的區別是,後者是從當前時間等待 nanos 秒時間,而前者是指定一個時間點,比如 2022-01-19 12:00:00。

總結

與 Object 類的 wait/notify 機制相比,park/unpark 有兩個優點:

  • 以 thread 為操作物件更符合阻塞執行緒的直觀定義
  • 操作更精準,可以準確地喚醒某一個執行緒(notify 隨機喚醒一個執行緒,notifyAll 喚醒所有等待的執行緒),增加了靈活性

相關文章