ReentrantLock 使用

黄橙發表於2024-03-13

ReentrantLock 介紹

ReentrantLock 是 Java 中的一個可重入鎖,它提供了與 synchronized 關鍵字類似的功能,但相比 synchronized,ReentrantLock 提供了更多的靈活性和功能。

定義:ReentrantLock 是一個可重入且獨佔式的鎖,它具有與使用 synchronized 監視器鎖相同的基本行為和語義,但與 synchronized 關鍵字相比,它更靈活、更強大,增加了輪詢、超時、中斷等高階功能。

ReentrantLock,顧名思義,它是支援可重入鎖的鎖,是一種遞迴無阻塞的同步機制。除此之外,該鎖還支援獲取鎖時的公平和非公平選擇。

公平性:ReentrantLock 的內部類 Sync 繼承了 AQS,分為公平鎖 FairSync 和非公平鎖 NonfairSync。

如果在絕對時間上,先對鎖進行獲取的請求一定先被滿足,那麼這個鎖是公平的,反之,是不公平的。公平鎖的獲取,也就是等待時間最長的執行緒最優先獲取鎖,也可以說鎖獲取是順序的。

ReentrantLock 的公平與否,可以透過它的建構函式來決定。

ReentrantLock 的主要特點包括:

  1. 可重入性:允許執行緒多次獲取同一把鎖,而不會造成死鎖。
  2. 公平性:可以選擇是否公平地獲取鎖,公平性可以保證等待時間最長的執行緒優先獲取鎖。
  3. 中斷響應:支援執行緒在等待鎖的過程中響應中斷。
  4. 條件變數:可以使用條件變數來實現更復雜的執行緒通訊。
  5. 鎖超時:可以設定獲取鎖的超時時間。

ReentrantLock 的主要方法包括:

  1. lock():獲取鎖,如果鎖已經被其他執行緒獲取,則當前執行緒會被阻塞,直到獲取到鎖。
  2. unlock():釋放鎖。
  3. tryLock():嘗試獲取鎖,如果獲取成功返回 true,否則返回 false。
  4. newCondition():建立一個 Condition 物件,用於執行緒等待和通知。
@Slf4j
public class LockExample2 {

    // 請求總數
    public static int clientTotal = 5000;
    // 同時併發執行的執行緒數
    public static int threadTotal = 200;

    public static int count = 0;
    private final static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

tryLock 方法

我們之前進行過介紹,Lock 介面包含了兩種 tryLock 方法,一種無引數,一種帶引數。

  • boolean tryLock():僅在呼叫時鎖為空閒狀態才獲取該鎖。如果鎖可用,則獲取鎖,並立即返回值 true。如果鎖不可用,則此方法將立即返回值 false;
  • boolean tryLock(long time, TimeUnit unit):如果鎖在給定的等待時間內空閒,並且當前執行緒未被中斷,則獲取鎖;
 try {
            if(locks.tryLock(4000,TimeUnit.MILLISECONDS)){ //嘗試獲取鎖,獲取成功則進入執行,不成功則執行finally模組
                System.out.println(Thread.currentThread().getName()+"-->");
                Thread.sleep(5000);
            }else{
                System.out.println(Thread.currentThread().getName()+" time out ");
            }
        } catch (InterruptedException e) {
             e.printStackTrace();
        }finally {
            try {
                locks.unlock();
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "未獲取到鎖,釋放鎖丟擲異常");
            }
        }

結果分析:tryLock 方法,雖然等待 4000 毫秒,但是這段時間不足以等待 Thread-1 釋放資源鎖,所以還是超時。

公平鎖與非公平鎖

分類:根據執行緒獲取鎖的搶佔機制,鎖可以分為公平鎖和非公平鎖。

公平鎖:表示執行緒獲取鎖的順序是按照執行緒請求鎖的時間早晚來決定的,也就是最早請求鎖的執行緒將最早獲取到鎖。

非公平鎖:非公平鎖則在執行時闖入,不遵循先到先執行的規則。

ReentrantLock:ReentrantLock 提供了公平和非公平鎖的實現。

ReentrantLock 例項:

//公平鎖
ReentrantLock pairLock = new ReentrantLock(true);
//非公平鎖
ReentrantLock pairLock1 = new ReentrantLock(false);
//如果建構函式不傳遞引數,則預設是非公平鎖。
ReentrantLock pairLock2 = new ReentrantLock();

場景介紹:透過模擬一個場景假設,來了解公平鎖與非公平鎖。

  • 假設執行緒 A 已經持有了鎖,這時候執行緒 B 請求該鎖將會被掛起;
  • 當執行緒 A 釋放鎖後,假如當前有執行緒 C 也需要獲取該鎖,如果採用非公平鎖方式,則根據執行緒排程策略,執行緒 B 和執行緒 C 兩者之一可能獲取鎖,這時候不需要任何其他干涉;
  • 而如果使用公平鎖則需要把 C 掛起,讓 B 獲取當前鎖,因為 B 先到所以先執行。

Tips:在沒有公平性需求的前提下儘量使用非公平鎖,因為公平鎖會帶來效能開銷。

ReentrantLock 其他方法介紹

對 ReentrantLock 來說,方法很多樣,如下介紹 ReentrantLock 其他的方法,有興趣的同學可以自行的嘗試使用。

  • getHoldCount():當前執行緒呼叫 lock () 方法的次數;
  • getQueueLength():當前正在等待獲取 Lock 鎖的執行緒的估計數;
  • getWaitQueueLength(Condition condition):當前正在等待狀態的執行緒的估計數,需要傳入 Condition 物件;
  • hasWaiters(Condition condition):查詢是否有執行緒正在等待與 Lock 鎖有關的 Condition 條件;
  • hasQueuedThread(Thread thread):查詢指定的執行緒是否正在等待獲取 Lock 鎖;
  • hasQueuedThreads():查詢是否有執行緒正在等待獲取此鎖定;
  • isFair():判斷當前 Lock 鎖是不是公平鎖;
  • isHeldByCurrentThread():查詢當前執行緒是否保持此鎖定;
  • isLocked():查詢此鎖定是否由任意執行緒保持。

相關文章