並發工具類Condition介紹與源碼解析

2021-01-07 IT樂知

在之前介紹AQS源碼的時候,還遺留了一個內部類ConditionObject沒有介紹,它也是並發中至關重要的類。

主要作用

Condition主要提供多個await方法以及signal、signalAll方法,對標的是Object的wait、notify、notifyAll方法,對應的作用也一樣。

ConditionObject是AQS的內部類並且實現了接口Condition,主要提供的功能也一樣。

創建方法

只有ReentrantLock提供了獲取AQS的Condition對象的方法。首先要new一個ReentrantLock對象,然後通過ReentrantLock的newCondition()方法獲取Condition對象,每次獲取都是創建一個新的Condition對象。

結構與主要方法

Condition(表示AQS中的ConditionObject,下同)只有兩個屬性firstWaiter、lastWaiter,類型是AQS的Node,也就是Condition也維護一個鍊表,firstWaiter、lastWaiter分別是鍊表的首位節點。

await方法源碼分析

Condition提供了很多個await方法,主要區別在於是否在如果等待一定時間後不是否繼續等待,這裡分析下await最基礎的無參方法,原始碼流程圖如下圖:

簡單分析下await方法:

首先創建一個Node(當前線程,狀態-2)節點並加到等待鍊表的尾部,這個Node是AQS中的Node,也會保存當前線程;

第二步是釋放鎖,因為線程在等待過程中其他線程也需要使用lock,所以這裡先釋放鎖,也就是修改AQS中state的值。

第三步是進入一個while循環,跳出循環判斷的條件是節點狀態不等於-2、並且要在同步隊列中(在AQS維護的鍊表中),如果沒在同步隊列中則會進入循環掛起線程,當再次被喚醒的時候會優先判斷線程是否中斷,如果線程中斷也會跳出循環,否則繼續循環判斷。

第四步是噹噹前線程跳出循環時,會重新設置鎖,設置成功再去根據情況整理鍊表,如果線程是中斷的,則根據情況拋出中斷異常,或者中斷。

從源碼中可以得出,在調用await方法之前一定要先獲取到鎖,否則會拋出異常。

signal方法:讓線程跳出循環

知道了await方法的源碼流程,就可以猜測signal方法的源碼了,先來分析下await方法如何才能執行完,先不考慮中斷線程的情況,要讓線程跳出while循環需要兩個條件:節點狀態不能是-2、讓節點在同步隊列中;記住這裡的同步隊列是AQS維護的同步隊列。

所以現在再來看signal方法的源碼就簡單了:

首先是拿到firstWaiter,如果為null則繼續往鍊表後面找;

第二步是把firstWaiter的狀態從等待狀態改為0,如果修改失敗則直接返回false,說明這個節點以及被修改成其他狀態了,繼續尋找下一個節點。

第三步把節點加到同步隊列的尾部;

第四步根據同步隊列的前置節點狀態判斷是否需要直接喚醒當前節點中的線程;

signal方法就是把Condition中維護的鍊表節點的頭部節點狀態設置為0並且加到AQS的同步隊列中。signalAll方法就是把鍊表的所有節點走一下signal方法的流程。

Condition與Object

分析了Condition方法但是並沒有體現它相比於Object的wait、notify、notifyAll的優勢,實際上這個問題在之前的文章設計阻塞隊列中有對比,這裡大概講一下,阻塞隊列有put與take方法,當隊列滿了put方法會阻塞,當隊列是空時take方法會阻塞。

用Object的wait、notifyAll方法來實現put、take方法的阻塞,在多線程情況下一個put方法獲取到鎖,那麼所有的包括put方法和take方法都會阻塞,當put方法執行完成後會喚醒take線程,但同時put線程也會被喚醒來搶執行權,但是如果此時隊列已滿,實際上所有的put線程都不應該會喚醒。

如果採用ReentrantLock來new出兩個Condition來實現,put方法如果發現隊列滿了則調用一個Condition的await方法來阻塞線程,另外一個來阻塞take方法,當put方法執行完後只用調用阻塞take方法中的Condition的signal,只用喚醒隊列中最前面一個就夠了。

總的來說可以利用一個ReentrantLock來實現對資源的訪問控制,利用多個Condition來更加精準的控制線程的阻塞與喚醒,相比利用一個Object來控制更加準確,可以使程序運行的更加高效。

總結

最後再梳理下Condition的await、signal方法,再使用await方法之前必須調用Condition對應的ReentrantLock的lock方法,也就是必須要獲取鎖。線程要跳出await方法就必須把節點放到AQS中的同步隊列中。而signal方法就是把節點放到同步隊列中。

再整理下整個流程:lock方法直到獲取鎖、調用await方法(生成一個Node放到Condition鍊表的末尾)、然後釋放鎖、進入while循環掛起線程、另外的線程獲取到鎖調用signal修改Condition鍊表的頭部節點狀態並放到AQS同步隊列中、當其他線程釋放鎖會去喚醒AQS同步隊列頭部線程、如果這個節點被喚醒就繼續執行await的while判斷會跳出循環、設置鎖狀態await方法執行完成、最後再調用unlock方法釋放鎖。

ReentrantLock用來維護同步隊列保證只有一個線程訪問資源、await方法把保存線程的節點加到Condition維護的鍊表中,signal方法把節點加到同步隊列中,通過這樣無論是await方法被喚醒還是其他地方ReentrantLock調用了lock都能保證資源只被最多一個線程訪問。

ReentrantLock與Condition在其他並發類中經常遇到,值得弄得他們的原理與優勢,便於理解其他並發類。

Java程式設計師日常學習筆記,如理解有誤歡迎各位交流討論!

相關焦點

  • 並發工具類閉鎖CountDownLatch介紹與源碼解析
    前面看了ReentrantLock的源碼,而閉鎖CountDownLatch可能大家見得少,但是他也是AQS下的一個並發工具類,今天來簡單介紹一下它。接下來我們來看稍微複雜的一個經典例子,代碼如下圖:這個方法可以實現指定並發數的情況下任務執行用時,因為創建線程有時間差,所以所有線程創建好先阻塞,等待一起喚醒。然後利用endLatch來控制統計線程的阻塞與喚醒,實現了很精確的計算並發下任務執行用時。
  • Java並發工具三劍客之CountDownLatch源碼解析
    CountDownLatch是Java並發包下的一個工具類,latch是門閂的意思,顧名思義,CountDownLatch就是有一個門閂擋住了裡面的人(線程)出來,當count減到0的時候,門閂就打開了,人(線程)就可以出來了。
  • Java並發編程系列21|Condition-Lock的等待通知
    本文將從以下幾個方面介紹 Condition:如何使用 Condition源碼分析Condition 的應用場景1.Condition 的使用1.1 Condition 類提供的方法等待方法:// 當前線程進入等待狀態,如果其他線程調用 condition 的 signal 或者 signalAll 方法並且當前線程獲取 Lock 從 await 方法返回,如果在等待狀態中被中斷會拋出被中斷異常
  • 一文弄懂最複雜並發工具類讀寫鎖源碼
    前面幾篇文章分析了AQS下實現類的使用,今天講最後一個也是最複雜的一個ReentrantReadWriteLock。主要功能ReentrantLock同一時刻只支持一個線程擁有鎖,但在多數情況下都是對數據的訪問而不是修改,訪問並不會修改數據並不影響其他線程對資源的訪問,所以不應該是獨佔的方式擁有資源,而是用共享的方式支持對資源並發訪問,提高系統的吞吐量。
  • 阿里P9都窺視已久的「Java並發實現原理:JDK源碼剖析」
    而從JDK 1.5開始,並發編程大師Doug Lea奉上了一個系統而全面的並發編程框架——JDK Concurrent包,裡面包含了各種原子操作、線程安全的容器、線程池和異步編程等內容。本書基於JDK 7和JDK 8,對整個Concurrent包進行全面的源碼剖析。JDK 8中大部分並發功能的實現和JDK 7一樣,但新增了一些額外特性。
  • 拼多多JDK源碼大揭秘,由淺入深看源碼,探究Java並發原理
    寫在前面幾乎所有的大神都會強調看源碼,也強調源碼的重要性;但是如何看源碼,源碼看什麼?看了什麼用?看了怎麼用?困擾很多人,尤其是初學者。如何閱讀源碼,是每個程式設計師需要面臨的一項挑戰。為什麼需要閱讀源碼?
  • 阿里P8架構師力薦的 Java源碼解析及面試合集
    源碼解析和設計思路06 LinkedList 源碼解析07 List 源碼會問哪些面試題08 HasMap源碼解析>第3章 並發集合類15 Copy0nWriteArrayList 源碼解析和設計思路16 CongurrentHashMap 源碼解析和設計思路
  • 復盤B站面試坑我最深的Java並發:JDK源碼剖析
    JDK源碼對於人腦的認知來說,「代碼一行行串行」當然最容易理解。但在多線程下,多個線程的代碼交叉並行,要訪問互斥資源,要互相通信。作為開發者,需要仔細設計線程之間的互斥與同步,稍不留心,就會寫出非線程安全的代碼。
  • Java並發編程系列23|循環屏障CyclicBarrier
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本篇介紹第二個並發工具類CyclicBarrier,CyclicBarrier的字面意思是可循環使用(Cyclic)的屏障(Barrier),分以下部分介紹:
  • Java並發編程之 Condition 源碼分析
    感謝原作者,本文轉載自 https://my.oschina.net/u/945573/blog/2995600Condition 介紹
  • C++並發condition_variable
    condition_variable 類是同步原語,能用於阻塞一個線程,或同時阻塞多個線程,直至另一線程修改共享變量(條件)並通知 condition_variable
  • 你知道JDK中Condition到底是個什麼東西麼?
    )經過ReentrantLock核心原理和源碼探索後,下面我們簡單的看下幾個並發組件的原理。                System.out.println("第一個線程加鎖");                lock.lock();                try {                    System.out.println("第一個線程釋放鎖以及阻塞等待");                    condition.await
  • Java多線程並發工具類-信號量Semaphore對象講解
    Java多線程並發工具類-Semaphore對象講解通過前面的學習,我們已經知道了Java多線程並發場景中使用比較多的兩個工具類:做加法的CycliBarrier對象以及做減法的CountDownLatch對象並對這兩個對象進行了比較。我們發現這兩個對象要麼是做加法,要麼是做減法的。那麼有沒有既做加法也做減法的呢?
  • Java多線程並發編程中並發容器第二篇之List的並發類講解
    Java多線程並發編程中並發容器第二篇之List的並發類講解概述本文我們將詳細講解list對應的並發容器以及用代碼來測試ArrayList、vector以及CopyOnWriteArrayList在100個線程向list中添加1000個數據後的比較
  • Java並發編程之支持並發的list集合你知道嗎
    Java並發編程之-list集合的並發.我們都知道Java集合類中的arrayList是線程不安全的。那麼怎麼證明是線程不安全的呢?怎麼解決在並發環境下使用安全的list集合類呢?本篇是《凱哥(凱哥Java:kagejava)並發編程學習》系列之《並發集合系列》教程的第一篇:本文主要內容:怎麼證明arrayList不是線程安全的?怎麼解決這個問題?以及遇到問題解決的四個步驟及從源碼來分析作者思路。一:怎麼證明arrayList在並發情況下是線程不安全的呢?
  • 狂神說java之JUC並發編程(一)
    1、什麼是JUC學習可以參考:源碼+官方文檔 進行學習文檔地址:https://docs.oracle.com/javase/8/docs/api/img首先我們看看什麼是JUC於是我們可以總結下JUC可以分為五大類1、同步工具類
  • Java中的並發工具類CountDownLatch
    Java中的並發工具類在多線程編程的時候,有時候需要控制並發流,Java本身提供了幾個控制並發的工具類,比如CountDownLatch,CyclicBarrier,SemaphoreSemaphore用來控制同時訪問資源的線程數量,比如用來限制流量,限制並發數等。acquire()方法是獲取一個通行證,releas()方法是歸還通行證。比如進小區的安檢,只能一個一個的來,代碼實現如下。
  • 社交媒體登錄Spring Social的源碼解析
    在上一篇文章中我們給大家介紹了OAuth2授權標準,並且著重介紹了OAuth2的授權碼認證模式。目前絕大多數的社交媒體平臺,都是通過OAuth2授權碼認證模式對外開放接口(登錄認證及用戶信息接口等)。但是,我們也看到OAuth2有一定的複雜性,如果所有的代碼都由我們自己開發,還是有一定的工作量的。
  • JAVA並發編程:線程並發工具類Callable、Future 和FutureTask的使用
    1、基本介紹  Runnable 是一個接口,在它裡面只聲明了一個 run()方法,由於 run()方法返回值為 void 類型,
  • 正確使用 wait/notify/notify方法以及源碼解析
    wait、notify、notifyAll前幾篇複習了下《線程的創建方式》、《線程的狀態》、《Thread 的源碼解析wait 方法源碼解析由於 wait () 是 Object 類的 native 方法,在 idea 中,它長這樣:public final native void wait(long timeout) throws InterruptedException;