首頁 > 軟體

執行緒阻塞喚醒工具 LockSupport使用詳解

2023-01-26 18:00:47

LockSupport 簡介

LockSupport 是 Java 並行程式設計中一個非常重要的元件,我們熟知的並行元件 Lock、執行緒池、CountDownLatch 等都是基於 AQS 實現的,而 AQS 內部控制執行緒阻塞和喚醒又是通過 LockSupport 來實現的。

從該類的註釋上也可以發現,它是一個控制執行緒阻塞和喚醒的工具,與以往的不同是它解決了曾經 wait()、notify()、await()、signal() 的侷限。

回顧 synchronized 和 Lock

我們知道 Java 中實現並行安全通常會通過這兩種加鎖的方式,對於 synchronized 加鎖的方式,如果我們想要控制執行緒的阻塞和喚醒是通過鎖物件的 wait()notify() 方法,以下面迴圈交替列印 AB 為例

int status = 2;
public static void main(String[] args) throws InterruptedException {
    TestSync obj = new TestSync();
     new Thread(() -> {
        synchronized (obj){
            while (true){
                if(obj.status == 1){
                    obj.wait();
                }
                System.out.println("A");
                obj.status = 1;
                TimeUnit.SECONDS.sleep(1);
                obj.notify();
            }
        }
     }).start();
    new Thread(() -> {
       synchronized (obj){
          while (true){
              if(obj.status == 2){
                  obj.wait();
              }
              System.out.println("B");
              obj.status = 2;
              TimeUnit.SECONDS.sleep(1);
              obj.notify();
          }
       }
    }).start();
}

如果我們使用 Lock 實現類,上述程式碼幾乎是一樣的,只是先獲取 Condition 物件

 Condition condition = lock.newCondition();

obj.wait() 換成 condition.await()obj.notify() 換成 condition.signal() 即可。

LockSupport 和 synchronized 和 Lock 的阻塞方式對比

技術阻塞喚醒方式侷限
synchronized使用鎖物件的 wait()、notify()1. 只能用在 synchronized 包裹的同步程式碼塊中 2. 必須先 wait() 才能 notify()
Lock使用 condition 的 await()、signal()1. 只能用在 lock 鎖住的程式碼塊中 2. 必須先 await() 才能 signal()
LockSupportpark()、unpark(Thread t)沒有限制

LockSupport 的使用

下面程式碼中,我們使用 LockSupport 去阻塞和喚醒執行緒,我們可以多次嘗試,LockSupportpark()unpark() 方法沒有先後順序的限制,也不需要捕獲異常,也沒有限制要在什麼程式碼塊中才能使用。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("A");
            LockSupport.park();
            System.out.println("被喚醒");
        });
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        new Thread(() -> {
            System.out.println("B");
            LockSupport.unpark(t1);
        }).start();
    }

LockSupport 注意事項

許可證提前發放

從該類的註釋中我們可以看到這個類儲存了使用它的執行緒的一個許可證,當呼叫 park() 方法的時候會判斷當前執行緒的許可證是否存在,如果存在將直接放行,否則就阻塞。

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("A");
        LockSupport.park();//不會阻塞
        System.out.println("被喚醒");
    });
    t1.start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        System.out.println("B");
        System.out.println("先呼叫 unpark()");
        LockSupport.unpark(t1);
    },"t2").start();
}

看這個程式碼範例,這裡我們在 t2 中先讓執行緒 t1 unpark(), 然後在 t1 中呼叫 park(), 結果並不會阻塞 t1 執行緒。因為在 t2 中呼叫 LockSupport.unpark(t1); 的時候相當於給 t1 提前準備好了許可證。

許可證不會累計

LockSupport.unpark(t1); 無論呼叫多少次,t1 的通行證只有一個,當在 t1 中呼叫兩次 park() 方法時執行緒依然會被阻塞。

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("A");
        LockSupport.park();
        LockSupport.park();
        System.out.println("被喚醒");
    });
    t1.start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        System.out.println("B");
        System.out.println("先呼叫 unpark()");
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
    },"t2").start();
}

以上述程式碼為例,t1 將被阻塞。

LockSupport 底層實現

觀察原始碼發現 park() 和 unpark() 最底下呼叫的是 native() 方法,原始碼在 C++ 中實現

@IntrinsicCandidate
public native void park(boolean isAbsolute, long time);
@IntrinsicCandidate
public native void unpark(Object thread);

對,這只是個標題,卷不動了,不去看 C/C++ 了。。。。

結語

LockSupport 是 Java 並行程式設計中非常重要的元件,這是我們下一步閱讀 AQS(AbstractQueuedSynchronizer) 原始碼的基礎。總之我們只要記住它是控制執行緒阻塞和喚醒的工具,並且知道它與其他阻塞喚醒方式的區別即可。

以上就是執行緒阻塞喚醒工具 LockSupport使用詳解的詳細內容,更多關於喚醒 LockSupport執行緒阻塞的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com