學(xué)Java的竟然有人不會(huì)AQS機(jī)制
Java中的并發(fā)包大家應(yīng)該都或多或少的了解過,說到并發(fā)包也就不得不提我們今天要說的AbstractQueuedSynchronizer,簡(jiǎn)稱AQS,這個(gè)是很多并發(fā)工具類的實(shí)現(xiàn)基礎(chǔ)
- public abstract class AbstractQueuedSynchronizer
- extends AbstractOwnableSynchronizer
- implements java.io.Serializable
類如其名,抽象的隊(duì)列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架,許多同步類實(shí)現(xiàn)都依賴于它,如常用的ReentrantLock、Semaphore、CountDownLatch
深入探究AQS
先來看這個(gè)圖,圖中有顏色的為Method,無(wú)顏色的為Attribution
總的來說,AQS框架共分為五層,自上而下由淺入深,從AQS對(duì)外暴露的API到底層基礎(chǔ)數(shù)據(jù)
當(dāng)有自定義同步器接入時(shí),只需重寫第一層所需要的部分方法即可,不需要關(guān)注底層具體的實(shí)現(xiàn)流程
道理也很簡(jiǎn)單,就像我們說的,這個(gè)東西是一個(gè)抽象的同步器,它將加鎖和解鎖這些操作交給了具體的實(shí)現(xiàn)類來自己實(shí)現(xiàn),就像這樣
當(dāng)自定義同步器進(jìn)行加鎖或者解鎖操作時(shí),先經(jīng)過第一層的API進(jìn)入AQS內(nèi)部方法,然后經(jīng)過第二層進(jìn)行鎖的獲取,獲取鎖成功之后便直接執(zhí)行相應(yīng)的邏輯,對(duì)于獲取鎖失敗的流程,進(jìn)入第三層和第四層的等待隊(duì)列處理,而這些處理方式均依賴于第五層的基礎(chǔ)數(shù)據(jù)提供層
這樣給大家說的話,應(yīng)該很容易就可以理解了
AQS的實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)
研究過AQS的同學(xué)應(yīng)該對(duì)這個(gè)圖都很熟悉了,AQS的核心就是state+Node+CLH變體雙向隊(duì)列
核心思想就是通過一個(gè)volatile類型state狀態(tài)來表示共享資源的狀態(tài),如果被請(qǐng)求的資源空閑,就將獲得共享資源的線程設(shè)置為當(dāng)前有效的線程,然后修改state為鎖定狀態(tài),其它的線程及時(shí)可見
共享資源被占用之后,其它線程肯定不能直接就返回失敗啊,這樣這個(gè)并發(fā)包的高效就沒得了,所以就引入了一個(gè)雙向隊(duì)列,這個(gè)雙向等待隊(duì)列放置那些暫時(shí)還未搶到共享資源的線程,來完成等待喚醒機(jī)制
實(shí)際上,AQS的運(yùn)行中的這個(gè)CLH變體的雙向隊(duì)列,不知存儲(chǔ)未搶到共享資源的線程,而搶到共享資源的這個(gè)線程也會(huì)作為隊(duì)列的頭節(jié)點(diǎn)head存在
CLH:Craig、Landin and Hagersten隊(duì)列,是單向鏈表,AQS中的隊(duì)列是CLH變體的虛擬雙向隊(duì)列(FIFO),AQS是通過將每條請(qǐng)求共享資源的線程封裝成一個(gè)節(jié)點(diǎn)來實(shí)現(xiàn)鎖的分配。
這么說大家應(yīng)該就很容易懂了吧,就是大家一起搶共享資源,搶到的就是有效線程,放到雙向隊(duì)列的head頭節(jié)點(diǎn),沒搶到的就依次往后排
我們接著看一下Node節(jié)點(diǎn)是怎么做的
這個(gè)是Node節(jié)點(diǎn)的屬性值和含義
簡(jiǎn)單解釋一下,waitStatus就是節(jié)點(diǎn)在隊(duì)列中的狀態(tài),Thread就是當(dāng)前節(jié)點(diǎn)的線程,prev和next是前驅(qū)指針和后繼指針
這里的重點(diǎn)就是waitStatus屬性
CANCELLED(1):表示當(dāng)前結(jié)點(diǎn)已取消調(diào)度。當(dāng)timeout或被中斷(響應(yīng)中斷的情況下),會(huì)觸發(fā)變更為此狀態(tài),進(jìn)入該狀態(tài)后的結(jié)點(diǎn)將不會(huì)再變化。
SIGNAL(-1):表示后繼結(jié)點(diǎn)在等待當(dāng)前結(jié)點(diǎn)喚醒。后繼結(jié)點(diǎn)入隊(duì)時(shí),會(huì)將前繼結(jié)點(diǎn)的狀態(tài)更新為SIGNAL。
CONDITION(-2):表示結(jié)點(diǎn)等待在Condition上,當(dāng)其他線程調(diào)用了Condition的signal()方法后,CONDITION狀態(tài)的結(jié)點(diǎn)將從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列中,等待獲取同步鎖。
PROPAGATE(-3):共享模式下,前繼結(jié)點(diǎn)不僅會(huì)喚醒其后繼結(jié)點(diǎn),同時(shí)也可能會(huì)喚醒后繼的后繼結(jié)點(diǎn)。
0:新結(jié)點(diǎn)入隊(duì)時(shí)的默認(rèn)狀態(tài)。
正是由于這個(gè)特點(diǎn),負(fù)值表示結(jié)點(diǎn)處于有效等待狀態(tài),而正值表示結(jié)點(diǎn)已被取消。所以源碼中很多地方用>0、<0來判斷結(jié)點(diǎn)的狀態(tài)是否正常
同步狀態(tài)state
AQS中維護(hù)了一個(gè)名為state的字段,意為同步狀態(tài),是由Volatile修飾的,用于展示當(dāng)前臨界資源的獲鎖情況。
- private volatile int state;
對(duì)于這個(gè)state,AQS也是提供了幾個(gè)方法
這幾個(gè)方法都是final類型的,子類是無(wú)法修改的
在AQS中的是有兩種加鎖模式的,一種是共享式,一種是獨(dú)占式,共享式也很簡(jiǎn)單,就是通過控制AQS中的state數(shù)值即可
state是AQS中的volatile類型,具有可見性,用于記錄加鎖狀態(tài)和重入的次數(shù),當(dāng)然不只是重入次數(shù),其實(shí)這個(gè)state在不同的實(shí)現(xiàn)類中是有不同的意義的
【ReentrantLock】:state用于記錄鎖的持有狀態(tài)和重入次數(shù),state=0表示沒有線程持有鎖;state=1表示有一個(gè)線程持有鎖;state=N表示exclusiveOwnerThread這個(gè)線程N(yùn)次重入了這個(gè)鎖。
【ReentrantReadWriteLock】:state用于記錄讀寫鎖的占用狀態(tài)和持有線程數(shù)量(讀鎖)、重入次數(shù)(寫鎖),state的高16位記錄持有讀鎖的線程數(shù)量,低16位記錄寫鎖線程重入次數(shù),如果這16位的值是0,表示沒有線程占用鎖,否則表示有線程持有鎖。
另外針對(duì)讀鎖,每個(gè)線程獲取到的讀鎖次數(shù)由本地線程變量中的HoldCounter記錄。
【Semaphore】:state用于計(jì)數(shù)。state=N表示還有N個(gè)信號(hào)量可以分配出去,state=0表示沒有信號(hào)量了,此時(shí)所有需要acquire信號(hào)量的線程都等著;
【CountDownLatch】:state也用于計(jì)數(shù),每次countDown都減一,減到0的時(shí)候喚醒被await阻塞的線程。
切記:區(qū)分開volatile類型的state屬性和Node節(jié)點(diǎn)中的waitStatus屬性
搶占共享資源也是有兩種方式的:公平鎖和非公平鎖
大家用過ReentrantLock的同學(xué)肯定都知道,默認(rèn)的是非公平鎖,但是我們可以傳入一個(gè)參數(shù)設(shè)置為公平鎖
按照ReentrantLock來說一下公平鎖和非公平鎖
公平鎖,是公平的,可以保證獲取鎖的線程按照先來后到的順序,獲取到鎖。
非公平鎖,各個(gè)線程獲取到鎖的順序,不一定和它們申請(qǐng)的先后順序一致,有可能后來的線程,反而先獲取到了鎖。
在實(shí)現(xiàn)上,公平鎖在進(jìn)行l(wèi)ock時(shí),首先會(huì)進(jìn)行tryAcquire()操作。
在tryAcquire中,會(huì)判斷等待隊(duì)列中是否已經(jīng)有別的線程在等待了。如果隊(duì)列中已經(jīng)有別的線程了,則tryAcquire失敗,則將自己加入隊(duì)列。
如果隊(duì)列中沒有別的線程,則進(jìn)行獲取鎖的操作。
非公平鎖,在進(jìn)行l(wèi)ock時(shí),會(huì)直接嘗試進(jìn)行加鎖,如果成功,則獲取到鎖,如果失敗,則進(jìn)行和公平鎖相同的動(dòng)作。
從公平鎖和非公平的實(shí)現(xiàn)上來看,他們的操作基本相同,唯一的區(qū)別在于,在lock時(shí),非公平鎖會(huì)直接先進(jìn)行嘗試加鎖的操作。
當(dāng)前一個(gè)線程完成了鎖的使用,并且釋放了,而且此時(shí)等待隊(duì)列非空時(shí),如果這是有新線程申請(qǐng)鎖,那么,公平鎖和非公平鎖的表現(xiàn)就會(huì)出現(xiàn)差異。
公平鎖
優(yōu)點(diǎn):線程按照順序獲取鎖,不會(huì)出現(xiàn)餓死現(xiàn)象(注:餓死現(xiàn)象是指一個(gè)線程的CPU執(zhí)行時(shí)間都被其他線程占用,導(dǎo)致得不到CPU執(zhí)行。
缺點(diǎn):整體吞吐效率相對(duì)非公平鎖要低,等待隊(duì)列中除一個(gè)線程以外的所有線程都會(huì)阻塞,CPU喚醒線程的開銷比非公平鎖要大。
非公平鎖
優(yōu)點(diǎn):可以減少喚起線程上下文切換的消耗,整體吞吐量比公平鎖高。
缺點(diǎn):在高并發(fā)環(huán)境下可能造成線程優(yōu)先級(jí)反轉(zhuǎn)和餓死現(xiàn)象。
AQS作為并發(fā)編程的框架,為很多其他同步工具提供了良好的解決方案。下面列出了JUC中的幾種同步工具,大體介紹一下AQS的應(yīng)用場(chǎng)景: