Condition 源码解读
一、Condition
在并發情況下進行線程間的協調,如果是使用的 synchronized 鎖,我們可以使用 wait/notify 進行喚醒,如果是使用的 Lock 鎖的方式,則可以使用 Condition 進行針對性的阻塞和喚醒,相較于 wait/notify 使用起來更靈活。那 Condition 是如何實現線程的等待和喚醒的呢,本篇文章帶領大家一起解讀下 Condition 的源碼。
在進行源碼分析前,先回顧下 Condition 是如何使用的,例如下面一個案例:
public class Test {public synchronized static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {lock.lock();System.out.println("線程1開始等待!");try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("線程1被喚醒繼續執行結束!");lock.unlock();}, "1").start();new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}lock.lock();System.out.println("開始喚醒線程!");condition.signal();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("線程2執行結束!");lock.unlock();}, "2").start();} }運行之后,可以看到下面日志:
由于在第一個線程中,使用的 condition.await() 因此當前線程會被阻塞掛起,而第二個線程,在 1s 后進行了 condition.signal() 操作,因此第一個線程會被喚醒繼續執行。這里細心的小伙伴應該可以發現,第一個線程阻塞時鎖并沒有釋放,而第二個線程在1s后也成功拿到鎖了,所以表明在 condition.await() 時會自動釋放當前鎖,這點和 wait 相同,在第二個線程進行了 condition.signal() 操作,第一個線程并沒有繼續向下執行,而是等待第二個線程處理完才會繼續執行,由此可以表明被喚醒的線程會重新獲取鎖,成功獲取鎖后繼續執行。
下面通過源碼看下 Condition 是如何實現的等待喚醒。
二、Condition 源碼解讀
2.1. lock.newCondition() 獲取 Condition 對象
首先看下在使用 lock.newCondition() 獲取一個Condition 對象時,具體做了什么,這里以 ReentrantLock 為例,進入到 ReentrantLock 的 newCondition() 方法中,又執行了 Sync 的 newCondition() 方法,再進去就會發現其實是 new 了一個 ConditionObject 類對象:
下面點到這個類中,可以看到其實是 AQS 下的一個子類:
2.2. condition.await() 阻塞過程
了解到 Condition 的對象后,可以看到是 AQS 下的一個子類,那下面其他的方法也肯定依賴于 AQS ,下面看下 condition.await() 方法,點到 await() 方法中:
其中 addConditionWaiter() 則是將自己加入到 AQS的隊列中,并獲取到當前線程所在的 Node ,這里注意下 Node 的狀態是 Node.CONDITION 也就是 -2,后面會依賴于該狀態。
下面再回到 await() 方法繼續向下看,接著使用了 fullyRelease 方法傳入了當前的 Node ,這里的 fullyRelease 方法主要做了釋放當前線程鎖的操作,可以看到又調用了 AQS 的 release 進行釋放資源,也就是釋放了當前所持有的鎖。
下面繼續回到 await() 方法中,當釋放鎖后,下面進入到了 while 循環中,通過查看 isOnSyncQueue 方法,可以看到是符合while的條件也就可以進入到循環中:
在循環中可以明顯的看到 LockSupport.park(this) ,將當前線程進行了阻塞。
2.3. condition.signal() 喚醒過程
上面已經看到線程被阻塞了,如果需要被喚醒則需要通過condition.signal(),這個方法是如何喚醒的呢?
下面來到 AbstractQueuedSynchronizer 類的 signal() 方法中:
主要執行了 doSignal 方法,再點到 doSignal 中,可以看到這里開啟了一個循環,對鏈表的每一個元素都進行了 transferForSignal 操作,這里也比較好理解,就是要喚醒等待中的線程。
下面點到 transferForSignal 中,看下對每個 Node 都做了什么操作。點進去之后也比較好理解,如果狀態是 Node.CONDITION 也就是 -2,剛才在解讀 await 方法時就提到這個狀態了,這里正好形成了呼應,下面有個非常顯眼的操作 LockSupport.unpark(node.thread) 直接喚醒了目標線程。也就是喚醒了 2.2 中的最后一步操作。
2.4. condition.await() 被喚醒后
當 await() 方法中的 LockSupport.park(this) 被喚醒后,繼續向下執行,下面會判斷下當前線程有沒有被打斷,如果沒被打斷則 break 終止循環繼續執行。
下面會使用 AQS 的 acquireQueued 方法,將先進入隊列的線程進行搶占鎖資源,如果成功獲取鎖后就會繼續執行,如果搶占失敗則繼續被掛起阻塞。
三、總結
通過上面的源碼分析,應該對 Condition 有了新的理解和掌握,細心地小伙伴應該可以發現在源碼中好多地方都使用了 CAS ,因此當競爭資源非常激烈時, Lock 的性能要遠遠優于 synchronized。
總結
以上是生活随笔為你收集整理的Condition 源码解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在微信、抖音上线一款小游戏?版权认证
- 下一篇: XGBoost(eXtreme Grad