J.U.C-AQS框架同步組件之可重入鎖ReentrantLock詳解

ReentrantLock?

synchronized是託管給JVM執行的,Lock的鎖定是通過代碼實現的。所以Lock比較靈活,可以便於開發人員根據合適的場景進行操作,Lock是一個接口,需要實現它來進行使用,ReetrantLock是Lock的主要實現類,ReetrantLock是一個可重入鎖,同時可以指定公平鎖非公平鎖,我們來具體看一下他的實現方式。

什麼是可重入鎖?

爲什麼叫重入鎖呢?
ReentrantLock,我們把它拆開來看就明瞭了。

Re-Entrant-Lock:即表示可重新反覆進入的鎖,但僅限於當前線程;

public void m() {
    lock.lock();
    lock.lock();
    try {
      // ... method body
    } finally {
      lock.unlock()
      lock.unlock()
    }
}

如示例代碼所示,當前線程可以反覆加鎖,但也需要釋放同樣加鎖次數的鎖,即重入了多少次,就要釋放多少次,不然也會導入鎖不被釋放。

試想一下,如果不設計成可重入鎖,那自己如果反覆給自己加鎖,不是會把自己加死鎖了嗎?所以,到現在,重入鎖的概念大概應該清楚了吧?

重入鎖最重要的幾個方法

這幾個方法都是 Lock 接口中定義的:
在這裏插入圖片描述

1. lock()

獲取鎖,有以下三種情況:

  • 鎖空閒:直接獲取鎖並返回,同時設置鎖持有者數量爲:1;
  • 當前線程持有鎖:直接獲取鎖並返回,同時鎖持有者數量遞增1;
  • 其他線程持有鎖:當前線程會休眠等待,直至獲取鎖爲止;
2. lockInterruptibly()

獲取鎖,邏輯和 lock() 方法一樣,但這個方法在獲取鎖過程中能響應中斷。

3. tryLock()

從關鍵字字面理解,這是在嘗試獲取鎖,獲取成功返回:true,獲取失敗返回:false, 這個方法不會等待,有以下三種情況:

  • 鎖空閒:直接獲取鎖並返回:true,同時設置鎖持有者數量爲:1;
  • 當前線程持有鎖:直接獲取鎖並返回:true,同時鎖持有者數量遞增1;
  • 其他線程持有鎖:獲取鎖失敗,返回:false;
4)tryLock(long timeout, TimeUnit unit)

邏輯和 tryLock() 差不多,只是這個方法是帶時間的。

5)unlock()

釋放鎖,每次鎖持有者數量遞減 1,直到 0 爲止。所以也就是爲什麼 lock 多少次,就要對應 unlock 多少次。

6)newCondition

返回一個這個鎖的 Condition 實例,可以實現 synchronized 關鍵字類似 wait/ notify 實現多線程通信的功能,不過這個比 wait/ notify 要更靈活,更強大!

代碼示例:

最簡單的加鎖
private static void add() {
        reentrantLock.lock();
        try {
            count++;
        } finally {
            reentrantLock.unlock();
        }
    }

加鎖和釋放鎖都在方法裏面進行,可以自由控制,比 synchronized 更靈活,更方便。但要注意的是,釋放鎖操作必須在 finally 裏面,不然如果出現異常導致鎖不能被正常釋放,進而會卡死後續所有訪問該鎖的線程。

ReentrantLockCondition組件搭配使用:
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LockExample6 {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {
            try {
                reentrantLock.lock();   // 此時被加入到了AQS等待序列中
                log.info("wait signal"); // 1
                condition.await();  // 又被AQS移除了(await操作釋放了鎖) 進入了Condition的等待隊列中 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get signal"); // 4 獲得信號
            reentrantLock.unlock();
        }).start();

        new Thread(() -> {
            reentrantLock.lock();  // 獲得鎖 進入AQS等待序列中
            log.info("get lock"); // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll(); // 發送信號喚醒所有等待的線程 將Condition等待隊列中的線程1節點的 取出並加入到AQS等待隊列中
            log.info("send signal ~ "); // 3
            reentrantLock.unlock();
        }).start();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章