初识Lock与AbstractQueuedSynchronizer(AQS)
本文轉(zhuǎn)載于:https://juejin.im/post/5aeb055b6fb9a07abf725c8c
一、concurrent包的結(jié)構(gòu)層次
在針對(duì)并發(fā)編程中,Doug Lea大師為我們提供了大量實(shí)用,高性能的工具類,針對(duì)這些代碼進(jìn)行研究會(huì)讓我們隊(duì)并發(fā)編程的掌握更加透徹也會(huì)大大提升我們隊(duì)并發(fā)編程技術(shù)的熱愛。這些代碼在java.util.concurrent包下。如下圖,即為concurrent包的目錄結(jié)構(gòu)圖。
其中包含了兩個(gè)子包:atomic以及l(fā)ock,另外在concurrent下的阻塞隊(duì)列以及executors,這些就是concurrent包中的精華,之后會(huì)一一進(jìn)行學(xué)習(xí)。而這些類的實(shí)現(xiàn)主要是依賴于volatile以及CAS,從整體上來看concurrent包的整體實(shí)現(xiàn)圖如下圖所示:
二、lock簡介
我們下來看concurent包下的lock子包。鎖是用來控制多個(gè)線程訪問共享資源的方式,一般來說,一個(gè)鎖能夠防止多個(gè)線程同時(shí)訪問共享資源。在Lock接口出現(xiàn)之前,java程序主要是靠synchronized關(guān)鍵字實(shí)現(xiàn)鎖功能的,而java SE5之后,并發(fā)包中增加了lock接口,它提供了與synchronized一樣的鎖功能。雖然它失去了像synchronize關(guān)鍵字隱式加鎖解鎖的便捷性,但是卻擁有了鎖獲取和釋放的可操作性,可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性。通常使用顯式使用lock的形式如下:
Lock lock = new ReentrantLock(); lock.lock(); try{....... }finally{lock.unlock(); }需要注意的是synchronized同步塊執(zhí)行完成或者遇到異常是鎖會(huì)自動(dòng)釋放,而lock必須調(diào)用unlock()方法釋放鎖,因此在finally塊中釋放鎖。
Lock接口定義的方法:
從最熟悉的ReentrantLock說起
很顯然ReentrantLock實(shí)現(xiàn)了lock接口,接下來我們來仔細(xì)研究一下它是怎樣實(shí)現(xiàn)的。當(dāng)你查看源碼時(shí)你會(huì)驚訝的發(fā)現(xiàn)ReentrantLock并沒有多少代碼,另外有一個(gè)很明顯的特點(diǎn)是:基本上所有的方法的實(shí)現(xiàn)實(shí)際上都是調(diào)用了其靜態(tài)內(nèi)存類Sync中的方法,而Sync類繼承了AbstractQueuedSynchronizer(AQS)。可以看出要想理解ReentrantLock關(guān)鍵核心在于對(duì)隊(duì)列同步器AbstractQueuedSynchronizer(簡稱同步器)的理解。
三、初識(shí)AQS
同步器是用來構(gòu)建鎖和其他同步組件的基礎(chǔ)框架,它的實(shí)現(xiàn)主要依賴一個(gè)int成員變量來表示同步狀態(tài)以及通過一個(gè)FIFO隊(duì)列構(gòu)成等待隊(duì)列。它的子類必須重寫AQS的幾個(gè)protected修飾的用來改變同步狀態(tài)的方法,其他方法主要是實(shí)現(xiàn)了排隊(duì)和阻塞機(jī)制。狀態(tài)的更新使用getState,setState以及compareAndSetState這三個(gè)方法。
子類被推薦定義為自定義同步組件的靜態(tài)內(nèi)部類,同步器自身沒有實(shí)現(xiàn)任何同步接口,它僅僅是定義了若干同步狀態(tài)的獲取和釋放方法來供自定義同步組件的使用,同步器既支持獨(dú)占式獲取同步狀態(tài),也可以支持共享式獲取同步狀態(tài),這樣就可以方便的實(shí)現(xiàn)不同類型的同步組件。
同步器是實(shí)現(xiàn)鎖(也可以是任意同步組件)的關(guān)鍵,在鎖的實(shí)現(xiàn)中聚合同步器,利用同步器實(shí)現(xiàn)鎖的語義。可以這樣理解二者的關(guān)系:鎖是面向使用者,它定義了使用者與鎖交互的接口,隱藏了實(shí)現(xiàn)細(xì)節(jié);同步器是面向鎖的實(shí)現(xiàn)者,它簡化了鎖的實(shí)現(xiàn)方式,屏蔽了同步狀態(tài)的管理,線程的排隊(duì),等待和喚醒等底層操作。鎖和同步器很好的隔離了使用者和實(shí)現(xiàn)者所需關(guān)注的領(lǐng)域。
AQS的設(shè)計(jì)是使用模板方法設(shè)計(jì)模式,它將一些方法開放給子類進(jìn)行重寫,而同步器給同步組件所提供模板方法又會(huì)重新調(diào)用被子類所重寫的方法。舉個(gè)例子,AQS中需要重寫的方法tryAcquire:
ReentrantLock中NonfairSync(繼承AQS)會(huì)重寫該方法為:
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); }而AQS中的模板方法acquire():
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}會(huì)調(diào)用tryAcquire方法,而此時(shí)當(dāng)繼承AQS的NonfairSync調(diào)用模板方法acquire時(shí)就會(huì)調(diào)用已經(jīng)被NonfairSync重寫的tryAcquire方法。這就是使用AQS的方式,在弄懂這點(diǎn)后會(huì)lock的實(shí)現(xiàn)理解有很大的提升。可以歸納總結(jié)為這么幾點(diǎn):
1.同步組件(這里不僅僅值鎖,還包括CountDownLatch等)的實(shí)現(xiàn)依賴于同步器AQS,在同步組件實(shí)現(xiàn)中,使用AQS的方式被推薦定義繼承AQS的靜態(tài)內(nèi)存類;
2.AQS采用模板方法進(jìn)行設(shè)計(jì),AQS的protected修飾的方法需要由繼承AQS的子類進(jìn)行重寫實(shí)現(xiàn),當(dāng)調(diào)用AQS的子類的方法時(shí)就會(huì)調(diào)用被重寫的方法;
3.AQS負(fù)責(zé)同步狀態(tài)的管理,線程的排隊(duì),等待和喚醒這些底層操作,而Lock等同步組件主要專注于實(shí)現(xiàn)同步語義;
4.在重寫AQS的方式時(shí),使用AQS提供的getState(),setState(),compareAndSetState()方法進(jìn)行修改同步狀態(tài)
AQS可重寫的方法如下圖(摘自《java并發(fā)編程的藝術(shù)》一書):
在實(shí)現(xiàn)同步組件時(shí)AQS提供的模板方法如下圖:
獨(dú)占式獲取與釋放同步狀態(tài);
共享式獲取與釋放同步狀態(tài);
查詢同步隊(duì)列中等待線程情況;
同步組件通過AQS提供的模板方法實(shí)現(xiàn)自己的同步語義。
總結(jié)
以上是生活随笔為你收集整理的初识Lock与AbstractQueuedSynchronizer(AQS)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 错过SaaS,就是错过这个时代
- 下一篇: 深入理解AbstractQueuedSy