欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

圖解Java?ReentrantLock的條件變量Condition機制

 更新時間:2022年10月14日 10:41:31   作者:JAVA旭陽  
想必大家都使用過wait()和notify()這兩個方法把,他們主要用于多線程間的協(xié)同處理。而RenentrantLock也支持這樣條件變量的能力,而且相對于synchronized?更加強大,能夠支持多個條件變量,本文就來詳細說說

概述

想必大家都使用過wait()和notify()這兩個方法把,這兩個方法主要用于多線程間的協(xié)同處理,即控制線程之間的等待、通知、切換及喚醒。而RenentrantLock也支持這樣條件變量的能力,而且相對于synchronized 更加強大,能夠支持多個條件變量。

最好可以先閱讀ReentrantLock系列文章:

圖解Java ReentrantLock公平鎖和非公平鎖的實現(xiàn)

詳解Java ReentrantLock可重入,可打斷,鎖超時的實現(xiàn)原理

ReentrantLock條件變量使用

ReentrantLock類API

Condition newCondition(): 創(chuàng)建條件變量對象

Condition類API

  • void await(): 當(dāng)前線程從運行狀態(tài)進入等待狀態(tài),同時釋放鎖,該方法可以被中斷
  • void awaitUninterruptibly():當(dāng)前線程從運行狀態(tài)進入等待狀態(tài),該方法不能夠被中斷
  • void signal(): 喚醒一個等待在 Condition 條件隊列上的線程
  • void signalAll(): 喚醒阻塞在條件隊列上的所有線程
@Test
public void testCondition() throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();
    //創(chuàng)建新的條件變量
    Condition condition = lock.newCondition();
    Thread thread0 = new Thread(() -> {
        lock.lock();
        try {
            System.out.println("線程0獲取鎖");
            // sleep不會釋放鎖
            Thread.sleep(500);
            //進入休息室等待
            System.out.println("線程0釋放鎖,進入等待");
            condition.await();
            System.out.println("線程0被喚醒了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    });
    thread0.start();
    //叫醒
    Thread thread1 = new Thread(() -> {
        lock.lock();
        try {
            System.out.println("線程1獲取鎖");
            //喚醒
            condition.signal();
            System.out.println("線程1喚醒線程0");
        } finally {
            lock.unlock();
            System.out.println("線程1釋放鎖");
        }
    });
    thread1.start();

    thread0.join();
    thread1.join();
}

運行結(jié)果:

  • condition的wait和notify必須在lock范圍內(nèi)
  • 實現(xiàn)條件變量的等待和喚醒,他們必須是同一個condition。
  • 線程1執(zhí)行conidtion.notify()后,并沒有釋放鎖,需要等釋放鎖后,線程0重新獲取鎖成功后,才能繼續(xù)向下執(zhí)行。

圖解實現(xiàn)原理

await過程

1.線程0(Thread-0)一開始獲取鎖,exclusiveOwnerThread字段是Thread-0, 如下圖中的深藍色節(jié)點

2.Thread-0調(diào)用await方法,Thread-0封裝成Node進入ConditionObject的隊列,因為此時只有一個節(jié)點,所有firstWaiter和lastWaiter都指向Thread-0,會釋放鎖資源,NofairSync中的state會變成0,同時exclusiveOwnerThread設(shè)置為null。如下圖所示。

3.線程1(Thread-1)被喚醒,重新獲取鎖,如下圖的深藍色節(jié)點所示。

4.Thread-0被park阻塞,如下圖灰色節(jié)點所示:

源碼如下:

下面是await()方法的整體流程,其中LockSupport.park(this)進行阻塞當(dāng)前線程,后續(xù)喚醒,也會在這個程序點恢復(fù)執(zhí)行。

public final void await() throws InterruptedException {
     // 判斷當(dāng)前線程是否是中斷狀態(tài),是就直接給個中斷異常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 將調(diào)用 await 的線程包裝成 Node,添加到條件隊列并返回
    Node node = addConditionWaiter();
    // 完全釋放節(jié)點持有的鎖,因為其他線程喚醒當(dāng)前線程的前提是【持有鎖】
    int savedState = fullyRelease(node);
    
    // 設(shè)置打斷模式為沒有被打斷,狀態(tài)碼為 0
    int interruptMode = 0;
    
    // 如果該節(jié)點還沒有轉(zhuǎn)移至 AQS 阻塞隊列, park 阻塞,等待進入阻塞隊列
    while (!isOnSyncQueue(node)) {
        // 阻塞當(dāng)前線程,待會
        LockSupport.park(this);
        // 如果被打斷,退出等待隊列,對應(yīng)的 node 【也會被遷移到阻塞隊列】尾部,狀態(tài)設(shè)置為 0
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 邏輯到這說明當(dāng)前線程退出等待隊列,進入【阻塞隊列】
    
    // 嘗試槍鎖,釋放了多少鎖就【重新獲取多少鎖】,獲取鎖成功判斷打斷模式
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    
    // node 在條件隊列時 如果被外部線程中斷喚醒,會加入到阻塞隊列,但是并未設(shè) nextWaiter = null
    if (node.nextWaiter != null)
        // 清理條件隊列內(nèi)所有已取消的 Node
        unlinkCancelledWaiters();
    // 條件成立說明掛起期間發(fā)生過中斷
    if (interruptMode != 0)
        // 應(yīng)用打斷模式
        reportInterruptAfterWait(interruptMode);
}

將線程封裝成Node, 加入到ConditionObject隊列尾部,此時節(jié)點的等待狀態(tài)時-2。

private Node addConditionWaiter() {
    // 獲取當(dāng)前條件隊列的尾節(jié)點的引用,保存到局部變量 t 中
    Node t = lastWaiter;
    // 當(dāng)前隊列中不是空,并且節(jié)點的狀態(tài)不是 CONDITION(-2),說明當(dāng)前節(jié)點發(fā)生了中斷
    if (t != null && t.waitStatus != Node.CONDITION) {
        // 清理條件隊列內(nèi)所有已取消的 Node
        unlinkCancelledWaiters();
        // 清理完成重新獲取 尾節(jié)點 的引用
        t = lastWaiter;
    }
    // 創(chuàng)建一個關(guān)聯(lián)當(dāng)前線程的新 node, 設(shè)置狀態(tài)為 CONDITION(-2),添加至隊列尾部
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;		// 空隊列直接放在隊首【不用CAS因為執(zhí)行線程是持鎖線程,并發(fā)安全】
    else
        t.nextWaiter = node;	// 非空隊列隊尾追加
    lastWaiter = node;			// 更新隊尾的引用
    return node;
}

清理條件隊列中的cancel類型的節(jié)點,比如中斷、超時等會導(dǎo)致節(jié)點轉(zhuǎn)換為Cancel

// 清理條件隊列內(nèi)所有已取消(不是CONDITION)的 node,【鏈表刪除的邏輯】
private void unlinkCancelledWaiters() {
    // 從頭節(jié)點開始遍歷【FIFO】
    Node t = firstWaiter;
    // 指向正常的 CONDITION 節(jié)點
    Node trail = null;
    // 等待隊列不空
    while (t != null) {
        // 獲取當(dāng)前節(jié)點的后繼節(jié)點
        Node next = t.nextWaiter;
        // 判斷 t 節(jié)點是不是 CONDITION 節(jié)點,條件隊列內(nèi)不是 CONDITION 就不是正常的
        if (t.waitStatus != Node.CONDITION) { 
            // 不是正常節(jié)點,需要 t 與下一個節(jié)點斷開
            t.nextWaiter = null;
            // 條件成立說明遍歷到的節(jié)點還未碰到過正常節(jié)點
            if (trail == null)
                // 更新 firstWaiter 指針為下個節(jié)點
                firstWaiter = next;
            else
                // 讓上一個正常節(jié)點指向 當(dāng)前取消節(jié)點的 下一個節(jié)點,【刪除非正常的節(jié)點】
                trail.nextWaiter = next;
            // t 是尾節(jié)點了,更新 lastWaiter 指向最后一個正常節(jié)點
            if (next == null)
                lastWaiter = trail;
        } else {
            // trail 指向的是正常節(jié)點 
            trail = t;
        }
        // 把 t.next 賦值給 t,循環(huán)遍歷
        t = next; 
    }
}

fullyRelease方法將r讓Thread-0釋放鎖, 這個時候Thread-1就會去競爭鎖

// 線程可能重入,需要將 state 全部釋放
final int fullyRelease(Node node) {
    // 完全釋放鎖是否成功,false 代表成功
    boolean failed = true;
    try {
        // 獲取當(dāng)前線程所持有的 state 值總數(shù)
        int savedState = getState();
        // release -> tryRelease 解鎖重入鎖
        if (release(savedState)) {
            // 釋放成功
            failed = false;
            // 返回解鎖的深度
            return savedState;
        } else {
            // 解鎖失敗拋出異常
            throw new IllegalMonitorStateException();
        }
    } finally {
        // 沒有釋放成功,將當(dāng)前 node 設(shè)置為取消狀態(tài)
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

判斷節(jié)點是否在AQS阻塞對列中,不在條件對列中

final boolean isOnSyncQueue(Node node) {
    // node 的狀態(tài)是 CONDITION,signal 方法是先修改狀態(tài)再遷移,所以前驅(qū)節(jié)點為空證明還【沒有完成遷移】
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 說明當(dāng)前節(jié)點已經(jīng)成功入隊到阻塞隊列,且當(dāng)前節(jié)點后面已經(jīng)有其它 node,因為條件隊列的 next 指針為 null
    if (node.next != null)
        return true;
	// 說明【可能在阻塞隊列,但是是尾節(jié)點】
    // 從阻塞隊列的尾節(jié)點開始向前【遍歷查找 node】,如果查找到返回 true,查找不到返回 false
    return findNodeFromTail(node);
}

signal過程

1.Thread-1執(zhí)行signal方法喚醒條件隊列中的第一個節(jié)點,即Thread-0,條件隊列置空

2.Thread-0的節(jié)點的等待狀態(tài)變更為0, 重新加入到AQS隊列尾部。

3.后續(xù)就是Thread-1釋放鎖,其他線程重新?lián)屾i。

源碼如下:

signal()方法是喚醒的入口方法

public final void signal() {
    // 判斷調(diào)用 signal 方法的線程是否是獨占鎖持有線程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 獲取條件隊列中第一個 Node
    Node first = firstWaiter;
    // 不為空就將第該節(jié)點【遷移到阻塞隊列】
    if (first != null)
        doSignal(first);
}

調(diào)用doSignal()方法喚醒節(jié)點

// 喚醒 - 【將沒取消的第一個節(jié)點轉(zhuǎn)移至 AQS 隊列尾部】
private void doSignal(Node first) {
    do {
        // 成立說明當(dāng)前節(jié)點的下一個節(jié)點是 null,當(dāng)前節(jié)點是尾節(jié)點了,隊列中只有當(dāng)前一個節(jié)點了
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    // 將等待隊列中的 Node 轉(zhuǎn)移至 AQS 隊列,不成功且還有節(jié)點則繼續(xù)循環(huán)
    } while (!transferForSignal(first) && (first = firstWaiter) != null);
}

// signalAll() 會調(diào)用這個函數(shù),喚醒所有的節(jié)點
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    // 喚醒所有的節(jié)點,都放到阻塞隊列中
    } while (first != null);
}

調(diào)用transferForSignal()方法,先將節(jié)點的 waitStatus 改為 0,然后加入 AQS 阻塞隊列尾部,將 Thread-3 的 waitStatus 改為 -1。

// 如果節(jié)點狀態(tài)是取消, 返回 false 表示轉(zhuǎn)移失敗, 否則轉(zhuǎn)移成功
final boolean transferForSignal(Node node) {
    // CAS 修改當(dāng)前節(jié)點的狀態(tài),修改為 0,因為當(dāng)前節(jié)點馬上要遷移到阻塞隊列了
    // 如果狀態(tài)已經(jīng)不是 CONDITION, 說明線程被取消(await 釋放全部鎖失?。┗蛘弑恢袛啵纱驍?cancelAcquire)
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        // 返回函數(shù)調(diào)用處繼續(xù)尋找下一個節(jié)點
        return false;
    
    // 【先改狀態(tài),再進行遷移】
    // 將當(dāng)前 node 入阻塞隊列,p 是當(dāng)前節(jié)點在阻塞隊列的【前驅(qū)節(jié)點】
    Node p = enq(node);
    int ws = p.waitStatus;
    
    // 如果前驅(qū)節(jié)點被取消或者不能設(shè)置狀態(tài)為 Node.SIGNAL,就 unpark 取消當(dāng)前節(jié)點線程的阻塞狀態(tài), 
    // 讓 thread-0 線程競爭鎖,重新同步狀態(tài)
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

以上就是圖解Java ReentrantLock的條件變量Condition機制的詳細內(nèi)容,更多關(guān)于ReentrantLock條件變量Condition機制的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Spring Boot 使用slf4j+logback記錄日志配置

    詳解Spring Boot 使用slf4j+logback記錄日志配置

    本篇文章主要介紹了Spring Boot 使用slf4j+logback記錄日志配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • java生成jar包并且單進程運行的實例

    java生成jar包并且單進程運行的實例

    下面小編就為大家分享一篇java生成jar包并且單進程運行的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • JavaBean和Map轉(zhuǎn)換封裝類的方法

    JavaBean和Map轉(zhuǎn)換封裝類的方法

    下面小編就為大家?guī)硪黄狫avaBean和Map轉(zhuǎn)換封裝類的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-10-10
  • 基于@Bean修飾的方法參數(shù)的注入方式

    基于@Bean修飾的方法參數(shù)的注入方式

    這篇文章主要介紹了@Bean修飾的方法參數(shù)的注入方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • springboot使JUL實現(xiàn)日志管理功能

    springboot使JUL實現(xiàn)日志管理功能

    這篇文章主要介紹了springboot使JUL實現(xiàn)日志管理功能,本文分步驟給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • spring security 5.x實現(xiàn)兼容多種密碼的加密方式

    spring security 5.x實現(xiàn)兼容多種密碼的加密方式

    spring security針對該功能有兩種實現(xiàn)方式,一種是簡單的使用加密來保證基于 cookie 的 token 的安全,另一種是通過數(shù)據(jù)庫或其它持久化存儲機制來保存生成的 token。這篇文章主要給大家介紹了關(guān)于spring security 5.x實現(xiàn)兼容多種密碼的加密方式,需要的朋友可以參考下。
    2018-01-01
  • Java中線程中斷的幾種方法小結(jié)

    Java中線程中斷的幾種方法小結(jié)

    在Java中,線程中斷是一種協(xié)作機制,它通過設(shè)置線程的中斷標(biāo)志位來通知線程需要中斷,本文主要介紹了Java中線程中斷的幾種方法小結(jié),具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能

    SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能

    這篇文章主要介紹了SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能的實現(xiàn)代碼,感興趣的小伙伴們可以參考一下
    2016-06-06
  • Java ArrayList與LinkedList及HashMap容器的用法區(qū)別

    Java ArrayList與LinkedList及HashMap容器的用法區(qū)別

    這篇文章主要介紹了Java ArrayList與LinkedList及HashMap容器的用法區(qū)別,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-07-07
  • 利用stream sorted進行降序排序

    利用stream sorted進行降序排序

    這篇文章主要介紹了利用stream sorted進行降序排序,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03

最新評論