8、AbstractQueuedSynchronizer

學(xué)習(xí)AQS的必要性

隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器或AQS),是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,它使用了一個int成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作。并發(fā)包的大師(Doug Lea)期望它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)。

1、AQS使用方式和其中的設(shè)計模式

AQS的主要使用方式是繼承,子類通過繼承AQS并實現(xiàn)它的抽象方法來管理同步狀態(tài),在AQS里由一個int型的state來代表這個狀態(tài),在抽象方法的實現(xiàn)過程中免不了要對同步狀態(tài)進行更改,這時就需要使用同步器提供的3個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))來進行操作,因為它們能夠保證狀態(tài)的改變是安全的。


8.1.1state

在實現(xiàn)上,子類推薦被定義為自定義同步組件的靜態(tài)內(nèi)部類,AQS自身沒有實現(xiàn)任何同步接口,它僅僅是定義了若干同步狀態(tài)獲取和釋放的方法來供自定義同步組件使用,同步器既可以支持獨占式地獲取同步狀態(tài),也可以支持共享式地獲取同步狀態(tài),這樣就可以方便實現(xiàn)不同類型的同步組件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。

同步器是實現(xiàn)鎖(也可以是任意同步組件)的關(guān)鍵,在鎖的實現(xiàn)中聚合同步器??梢赃@樣理解二者之間的關(guān)系:

鎖是面向使用者的,它定義了使用者與鎖交互的接口(比如可以允許兩個線程并行訪問),隱藏了實現(xiàn)細(xì)節(jié);

同步器面向的是鎖的實現(xiàn)者,它簡化了鎖的實現(xiàn)方式,屏蔽了同步狀態(tài)管理、線程的排隊、等待與喚醒等底層操作。鎖和同步器很好地隔離了使用者和實現(xiàn)者所需關(guān)注的領(lǐng)域。

實現(xiàn)者需要繼承同步器并重寫指定的方法,隨后將同步器組合在自定義同步組件的實現(xiàn)中,并調(diào)用同步器提供的模板方法,而這些模板方法將會調(diào)用使用者重寫的方法。

模板方法模式

同步器的設(shè)計基于模板方法模式。模板方法模式的意圖是,定義一個操作中的算法的骨架,而將一些步驟的實現(xiàn)延遲到子類中。模板方法使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。我們最常見的就是Spring框架里的各種Template。

2、AQS中的方法

模板方法

實現(xiàn)自定義同步組件時,將會調(diào)用同步器提供的模板方法,


8.2.1模板方法

這些模板方法同步器提供的模板方法基本上分為3類:獨占式獲取與釋放同步狀態(tài)、共享式獲取與釋放、同步狀態(tài)和查詢同步隊列中的等待線程情況。

可重寫的方法

8.2.2 重寫?yīng)氄兼i


8.2.3重寫共享鎖


訪問或修改同步狀態(tài)的方法

重寫同步器指定的方法時,需要使用同步器提供的如下3個方法來訪問或修改同步狀態(tài)。

?getState():獲取當(dāng)前同步狀態(tài)。

?setState(int newState):設(shè)置當(dāng)前同步狀態(tài)。

?compareAndSetState(int expect,int update):使用CAS設(shè)置當(dāng)前狀態(tài),該方法能夠保證狀態(tài)設(shè)置的原子性。?

3、CLH隊列

CLH隊列鎖即Craig, Landin, and Hagersten (CLH) locks。

CLH隊列鎖也是一種基于鏈表的可擴展、高性能、公平的自旋鎖,申請線程僅僅在本地變量上自旋,它不斷輪詢前驅(qū)的狀態(tài),假設(shè)發(fā)現(xiàn)前驅(qū)釋放了鎖就結(jié)束自旋。

當(dāng)一個線程需要獲取鎖時:

1)、創(chuàng)建一個的QNode,將其中的locked設(shè)置為true表示需要獲取鎖,myPred表示對其前驅(qū)結(jié)點的引用

8.3.1 創(chuàng)建一個? QNode

2)、線程A對tail域調(diào)用getAndSet方法,使自己成為隊列的尾部,同時獲取一個指向其前驅(qū)結(jié)點的引用myPred

8.3.2獲取鎖

線程B需要獲得鎖,同樣的流程再來一遍

3)、線程就在前驅(qū)結(jié)點的locked字段上旋轉(zhuǎn),直到前驅(qū)結(jié)點釋放鎖(前驅(qū)節(jié)點的鎖值 locked == false)

4)、當(dāng)一個線程需要釋放鎖時,將當(dāng)前結(jié)點的locked域設(shè)置為false,同時回收前驅(qū)結(jié)點


8.3.3 釋放鎖

如上圖所示,前驅(qū)結(jié)點釋放鎖,線程A的myPred所指向的前驅(qū)結(jié)點的locked字段變?yōu)閒alse,線程A就可以獲取到鎖。

CLH隊列鎖的優(yōu)點是空間復(fù)雜度低(如果有n個線程,L個鎖,每個線程每次只獲取一個鎖,那么需要的存儲空間是O(L+n),n個線程有n個myNode,L個鎖有L個tail)。CLH隊列鎖常用在SMP體系結(jié)構(gòu)下。

Java中的AQS是CLH隊列鎖的一種變體實現(xiàn)。

4、ReentrantLock的實現(xiàn)

鎖的可重入

重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會被鎖所阻塞,該特性的實現(xiàn)需要解決以下兩個問題。

1)線程再次獲取鎖。鎖需要去識別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程,如果是,則再次成功獲取。

2)鎖的最終釋放。線程重復(fù)n次獲取了鎖,隨后在第n次釋放該鎖后,其他線程能夠獲取到該鎖。鎖的最終釋放要求鎖對于獲取進行計數(shù)自增,計數(shù)表示當(dāng)前鎖被重復(fù)獲取的次數(shù),而鎖被釋放時,計數(shù)自減,當(dāng)計數(shù)等于0時表示鎖已經(jīng)成功釋放。

nonfairTryAcquire方法增加了再次獲取同步狀態(tài)的處理邏輯:通過判斷當(dāng)前線程是否為獲取鎖的線程來決定獲取操作是否成功,如果是獲取鎖的線程再次請求,則將同步狀態(tài)值進行增加并返回true,表示獲取同步狀態(tài)成功。同步狀態(tài)表示鎖被一個線程重復(fù)獲取的次數(shù)。

如果該鎖被獲取了n次,那么前(n-1)次tryRelease(int releases)方法必須返回false,而只有同步狀態(tài)完全釋放了,才能返回true??梢钥吹剑摲椒▽⑼綘顟B(tài)是否為0作為最終釋放的條件,當(dāng)同步狀態(tài)為0時,將占有線程設(shè)置為null,并返回true,表示釋放成功。

公平和非公平鎖

ReentrantLock的構(gòu)造函數(shù)中,默認(rèn)的無參構(gòu)造函數(shù)將會把Sync對象創(chuàng)建為NonfairSync對象,這是一個“非公平鎖”;而另一個構(gòu)造函數(shù)ReentrantLock(boolean fair)傳入?yún)?shù)為true時將會把Sync對象創(chuàng)建為“公平鎖”FairSync。

nonfairTryAcquire(int acquires)方法,對于非公平鎖,只要CAS設(shè)置同步狀態(tài)成功,則表示當(dāng)前線程獲取了鎖,而公平鎖則不同。tryAcquire方法,該方法與nonfairTryAcquire(int acquires)比較,唯一不同的位置為判斷條件多了hasQueuedPredecessors()方法,即加入了同步隊列中當(dāng)前節(jié)點是否有前驅(qū)節(jié)點的判斷,如果該方法返回true,則表示有線程比當(dāng)前線程更早地請求獲取鎖,因此需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容