中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JUC的ReentrantLock怎么使用

發布時間:2021-12-21 10:18:51 來源:億速云 閱讀:118 作者:iii 欄目:大數據

這篇文章主要講解了“JUC的ReentrantLock怎么使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“JUC的ReentrantLock怎么使用”吧!

ReentrantLock 譯為可重入鎖,我們在使用時總是將其與 synchronized 關鍵字進行對比,實際上 ReentrantLock 與 synchronized 關鍵字在使用上具備相同的語義,區別僅在于 ReentrantLock 相對于 synchronized 關鍵字留給開發者的可操作性更強,所以在使用上更加靈活,當然凡事都有兩面,靈活的背后也暗藏著更加容易出錯的風險。

盡管語義相同,但 ReentrantLock 和 synchronized 關鍵字背后的實現機制卻大相徑庭。前面的文章中我們分析了 synchronized 關鍵字的實現內幕,知道了 synchronized 關鍵字背后依賴于 monitor 技術,而本文所要分析的 ReentrantLock 在實現上則依賴于 AQS 隊列同步器,具體如何基于 AQS 進行實現,下面來一探究竟。

ReentrantLock 示例

本小節使用 ReentrantLock 實現一個 3 線程交替打印的程序,演示基于 ReentrantLock 實現鎖的獲取、釋放,以及線程之間的通知機制。示例實現如下:

private static Lock lock = new ReentrantLock(true);

private static Condition ca = lock.newCondition();
private static Condition cb = lock.newCondition();
private static Condition cc = lock.newCondition();

private static volatile int idx = 0;

private static class A implements Runnable {

    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                cb.signalAll();
                System.out.println("a: " + (++idx));
                ca.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

private static class B implements Runnable {

    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                cc.signalAll();
                System.out.println("b: " + (++idx));
                cb.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

private static class C implements Runnable {

    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                ca.signalAll();
                System.out.println("c: " + (++idx));
                cc.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public static void main(String[] args) {
    new Thread(new A()).start();
    new Thread(new B()).start();
    new Thread(new C()).start();
}

上述示例定義了 3 個線程類 A、B 和 C,并按照 A -> B -> C 的順序進行組織,各個線程在調用 Lock#lock 方法獲取到鎖之后會先嘗試通知后繼線程(將對應的線程移入到同步隊列),然后對 idx 變量進行累加并打印,接著進入等待狀態并釋放資源,方法 Lock#unlock 接下來會調度位于同步隊列隊頭結點的線程繼續執行。

ReentrantLock 實現內幕

Lock 接口

ReentrantLock 實現了 Lock 接口,該接口抽象了鎖應該具備的基本操作,包括鎖資源的獲取、釋放,以及創建條件對象。除了本文介紹的 ReentrantLock 外,JUC 中直接或間接實現了 Lock 接口的組件還包括 ReentrantReadWriteLock 和 StampedLock,我們將在后面的文章中對這些組件逐一分析。Lock 接口的定義如下:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

各方法釋義如下:

  • lock():獲取鎖資源,如果獲取失敗則阻塞。

  • lockInterruptibly():獲取鎖資源,如果獲取失敗則阻塞,阻塞期間支持響應中斷請求。

  • tryLock():嘗試獲取鎖資源,不管是否獲取成功都立即返回,如果獲取成功則返回 true,否則返回 false。

  • tryLock(long time, TimeUnit unit):嘗試獲取鎖資源,相對于無參版本的 tryLock 方法引入了超時機制,并支持在等待期間響應中斷請求。

  • unlock():釋放鎖資源。

  • newCondition():創建一個綁定到當前 Lock 上的條件對象。

資源的獲取與釋放

上一小節分析了 Lock 接口的定義,ReentrantLock 實現了該接口,并將接口方法的實現都委托給了 Sync 內部類處理。Sync 是一個抽象類,繼承自 AbstractQueuedSynchronizer,并派生出 FairSync 和 NonfairSync 兩個子類(繼承關系如下圖),由命名可以看出 FairSync 實現了公平鎖,而 NonfairSync 則實現了非公平鎖。

JUC的ReentrantLock怎么使用

ReentrantLock 提供了帶 boolean 參數的構造方法,依據該參數來決定是創建公平鎖還是非公平鎖(默認為非公平鎖),構造方法定義如下:

public ReentrantLock() {
    // 默認創建非公平鎖
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    // 依據參數決定創建公平鎖還是非公平鎖
    sync = fair ? new FairSync() : new NonfairSync();
}

下面將區分公平鎖和非公平鎖分析 ReentrantLock 針對 Lock 接口方法的具體實現,在開始之前先介紹一下 AQS 中的 state 字段在 ReentrantLock 中的作用。

我們知道 ReentrantLock 是可重入的,這里的可重入是指當一個線程獲取到 ReentrantLock 鎖之后,如果該線程再次嘗試獲取該 ReentrantLock 鎖時仍然可以獲取成功,對應的重入次數加 1。ReentrantLock 的重入次數則由 AQS 的 state 字段進行記錄。當 state 為 0 時,說明目標 ReentrantLock 鎖當前未被任何線程持有,當一個線程釋放 ReentrantLock 鎖時,對應的 state 值需要減 1。

非公平鎖

本小節我們來分析一下非公平鎖 NonfairSync 的實現機制,首先來看一下 NonfairSync#lock 方法,該方法用于獲取資源,如果獲取失敗則會將當前線程加入到同步隊列中阻塞等待。方法實現如下:

final void lock() {
    // 嘗試獲取鎖,將 state 由 0 設置為 1
    if (this.compareAndSetState(0, 1)) {
        // 首次獲取鎖成功,記錄當前鎖對象
        this.setExclusiveOwnerThread(Thread.currentThread());
    } else {
        // 目標鎖對象已經被占用,或者非首次獲取目標鎖對象
        this.acquire(1);
    }
}

方法 NonfairSync#lock 加鎖的過程首先會基于 CAS 操作嘗試將 ReentrantLock 的 state 值由 0 改為 1,搶占鎖資源,這也是非公平語義的根本所在。如果操作成功,則說明目標 ReentrantLock 鎖當前未被任何線程持有,且本次加鎖成功。如果操作失敗則區分兩種情況:

  • 目標 ReentrantLock 鎖已被當前線程持有。

  • 目標 ReentrantLock 鎖已被其它線程持有。

針對這兩種情況,接下來會調用 AbstractQueuedSynchronizer#acquire 方法嘗試獲取 1 個單位的資源,該方法由 AQS 實現,我們已經在前面的文章中分析過,其中會執行模板方法 AbstractQueuedSynchronizer#tryAcquire。NonfairSync 針對該模板方法的實現如下:

protected final boolean tryAcquire(int acquires) {
    return this.nonfairTryAcquire(acquires);
}

上述方法將嘗試獲取資源的邏輯委托給 Sync#nonfairTryAcquire 方法執行,ReentrantLock 的 ReentrantLock#tryLock() 方法同樣基于該方法實現。下面來分析一下該方法的執行邏輯,實現如下:

final boolean nonfairTryAcquire(int acquires) {
    // 獲取當前線程對象
    final Thread current = Thread.currentThread();
    // 獲取 state 值
    int c = this.getState();
    if (c == 0) {
        // state 為 0,表示目標鎖當前未被持有,嘗試獲取鎖
        if (this.compareAndSetState(0, acquires)) {
            this.setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果當前已經持有鎖的線程已經是當前線程
    else if (current == this.getExclusiveOwnerThread()) {
        // 重入次數加 1
        int nextc = c + acquires;
        if (nextc < 0) {
            // 重入次數溢出
            throw new Error("Maximum lock count exceeded");
        }
        // 更新 state 記錄的重入次數
        this.setState(nextc);
        return true;
    }
    // 已經持有鎖的線程不是當前線程,嘗試加鎖失敗
    return false;
}

方法 Sync#nonfairTryAcquire 的執行流程可以概括為;

  1. 獲取當前 ReentrantLock 鎖的 state 值;

  2. 如果 state 值為 0,說明當前 ReentrantLock 鎖未被任何線程持有,基于 CAS 嘗試將 state 值由 0 改為 1,搶占鎖資源,修改成功即為加鎖成功;

  3. 否則,如果當前已經持有該 ReentrantLock 鎖的線程是自己,則修改重入次數(即將 state 值加 1);

  4. 否則,目標 ReentrantLock 鎖已經被其它線程持有,加鎖失敗。

如果 Sync#nonfairTryAcquire 方法返回 false,則說明當前線程嘗試獲取目標 ReentrantLock 鎖失敗,對于 ReentrantLock#lock 方法而言,接下去線程會被加入到同步隊列阻塞等待,而對于 ReentrantLock#tryLock() 方法而言,線程會立即退出,并返回 false。

方法 ReentrantLock#newCondition 同樣是委托給 Sync#newCondition 方法處理,該方法只是簡單的創建了一個 ConditionObject 對象,即新建了一個條件隊列。非公平鎖 NonfairSync 中的以下方法都是直接委托給 AQS 處理,這些方法的實現機制已在前面分析 AQS 時介紹過:

  • ReentrantLock#lockInterruptibly:直接委托給 AbstractQueuedSynchronizer#acquireInterruptibly 方法實現,獲取的資源數為 1。

  • ReentrantLock#tryLock(long, java.util.concurrent.TimeUnit):直接委托給 AbstractQueuedSynchronizer#tryAcquireNanos 方法實現,獲取的資源數為 1。

  • ReentrantLock#unlock:直接委托給 AbstractQueuedSynchronizer#release 方法實現,釋放的資源數為 1。

前面的文章,我們在分析 AQS 的 AbstractQueuedSynchronizer#release 方法時,曾介紹過該方法會調用模板方法 AbstractQueuedSynchronizer#tryRelease 以嘗試釋放資源。ReentrantLock 針對該模板方法的實現位于 Sync 抽象類中,所以它是一個由 NonfairSync 和 FairSync 共用的方法,下面來分析一下該方法的實現。

protected final boolean tryRelease(int releases) {
    // 將當前 state 記錄的重入次數減 1
    int c = this.getState() - releases;
    // 如果當前持有鎖的線程對象不是當前線程則拋出異常
    if (Thread.currentThread() != this.getExclusiveOwnerThread()) {
        throw new IllegalMonitorStateException();
    }
    boolean free = false;
    // 如果重入次數已經降為 0,則清空持有當前鎖的線程對象
    if (c == 0) {
        free = true;
        this.setExclusiveOwnerThread(null);
    }
    // 更新當前鎖的重入次數
    this.setState(c);
    return free;
}

嘗試釋放資源的過程本質上就是修改 state 字段值的過程,如果當前操作的線程是持有 ReentrantLock 鎖的線程,則上述方法會將 state 值減 1,即將已重入次數減 1。如果修改后的 state 字段值為 0,則說明當前線程已經釋放了持有的 ReentrantLock 鎖,此時需要清除記錄在 ReentrantLock 對象中的線程 Thread 對象。

公平鎖

本小節我們來分析一下公平鎖 FairSync 的實現機制,這里的公平本質上是指公平的獲取鎖資源,所以主要的區別體現在加鎖的過程,即 ReentrantLock#lock 方法。

前面我們在分析 NonfairSync 時看到,NonfairSync 在加鎖時首先會基于 CAS 嘗試將 state 值由 0 改為 1,失敗的情況下才會繼續調用 AbstractQueuedSynchronizer#acquire 方法等待獲取資源,并且在同步隊列中等待期間仍然會在 state 為 0 時搶占獲取鎖資源。

FairSync 相對于 NonfairSync 的區別在于當 state 值為 0 時,即目標 ReentrantLock 鎖此時未被任何線程持有的情況下,FairSync 并不會去搶占鎖資源,而是檢查同步隊列中是否有排在前面等待獲取鎖資源的其它線程,如果有則讓渡這些排在前面的線程優先獲取鎖資源。

下面來看一下 FairSync#lock 方法的實現,該方法只是簡單的將獲取鎖資源操作委托給 AQS 的 AbstractQueuedSynchronizer#acquire 方法執行,所以我們需要重點關注一下模板方法 FairSync#tryAcquire 的實現:

protected final boolean tryAcquire(int acquires) {
        // 獲取當前線程對象
        final Thread current = Thread.currentThread();
        // 獲取當前 state 值
        int c = this.getState();
        if (c == 0) {
            // state 為 0,表示目標鎖當前未被持有,先檢查是否有阻塞等待當前鎖的線程,如果沒有再嘗試獲取鎖
            if (!this.hasQueuedPredecessors() && this.compareAndSetState(0, acquires)) {
                this.setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果當前已經持有鎖的線程已經是當前線程,則修改已重入次數加 1
        else if (current == this.getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) {
                throw new Error("Maximum lock count exceeded");
            }
            this.setState(nextc);
            return true;
        }
        return false;
    }
}

上述方法的執行流程與 NonfairSync 中的相關實現大同小異,主要區別在于當 state 值為 0 時,FairSync 會調用 AbstractQueuedSynchronizer#hasQueuedPredecessors 檢查當前同步隊列中是否還有等待獲取鎖資源的其它線程,如果存在則優先讓這些線程獲取鎖資源,并將自己加入到同步隊列中排隊等待。

感謝各位的閱讀,以上就是“JUC的ReentrantLock怎么使用”的內容了,經過本文的學習后,相信大家對JUC的ReentrantLock怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

绵竹市| 洞头县| 青州市| 嘉祥县| 襄垣县| 龙岩市| 永靖县| 建阳市| 汨罗市| 秭归县| 济源市| 淮滨县| 通化市| 霍山县| 新营市| 合水县| 乐安县| 保定市| 博野县| 嘉定区| 柳林县| 唐海县| 红河县| 万州区| 上蔡县| 青河县| 尉氏县| 米脂县| 邹城市| 秦皇岛市| 奉新县| 桂平市| 盐源县| 瑞丽市| 甘泉县| 加查县| 巧家县| 成武县| 青浦区| 岫岩| 新龙县|