扒一扒ReentrantLock以及AQS實(shí)現(xiàn)原理
這篇文章,我們來(lái)聊聊面試的時(shí)候比較有殺傷力的一個(gè)問(wèn)題:聊聊你對(duì)AQS的理解?
之前有同學(xué)反饋,去互聯(lián)網(wǎng)公司面試,面試官聊到并發(fā)時(shí)就問(wèn)到了這個(gè)問(wèn)題。當(dāng)時(shí)那位同學(xué)內(nèi)心估計(jì)受到了一萬(wàn)點(diǎn)傷害。。。
因?yàn)槭紫?,很多人還真的連AQS是什么都不知道,可能聽(tīng)都沒(méi)聽(tīng)說(shuō)過(guò)?;蛘哂械娜寺?tīng)說(shuō)過(guò)AQS這個(gè)名詞,但是可能連具體全稱怎么拼寫(xiě)都不知道。
更有甚者,可能會(huì)說(shuō):AQS?是不是一種思想?我們平時(shí)開(kāi)發(fā)怎么來(lái)用AQS?
總體來(lái)說(shuō),很多同學(xué)估計(jì)都對(duì)AQS有一種云里霧里的感覺(jué),如果用搜索引擎查一下AQS是什么?看幾篇文章,估計(jì)就直接放棄了,因?yàn)槊苊苈槁榈奈淖?,?shí)在是看不懂!
所以,基于上述痛點(diǎn),咱們這篇文章,就用最簡(jiǎn)單的大白話配合N多張手繪圖,給大家講清楚AQS到底是什么?讓各位同學(xué)面試被問(wèn)到這個(gè)問(wèn)題時(shí),不至于不知所措。
ReentrantLock和AQS的關(guān)系
首先我們來(lái)看看,如果用Java并發(fā)包下的ReentrantLock來(lái)加鎖和釋放鎖,是個(gè)什么樣的感覺(jué)?
這個(gè)基本學(xué)過(guò)Java的同學(xué)應(yīng)該都會(huì)吧,畢竟這個(gè)是java并發(fā)基本API的使用,應(yīng)該每個(gè)人都是學(xué)過(guò)的,所以我們直接看一下代碼就好了:
上面那段代碼應(yīng)該不難理解吧,無(wú)非就是搞一個(gè)Lock對(duì)象,然后加鎖和釋放鎖。
你這時(shí)可能會(huì)問(wèn),這個(gè)跟AQS有啥關(guān)系?關(guān)系大了去了!因?yàn)镴ava并發(fā)包下很多API都是基于AQS來(lái)實(shí)現(xiàn)的加鎖和釋放鎖等功能的,AQS是Java并發(fā)包的基礎(chǔ)類。
舉個(gè)例子,比如說(shuō)ReentrantLock、ReentrantReadWriteLock底層都是基于AQS來(lái)實(shí)現(xiàn)的。
那么AQS的全稱是什么呢?AbstractQueuedSynchronizer,抽象隊(duì)列同步器。給大家畫(huà)一個(gè)圖先,看一下ReentrantLock和AQS之間的關(guān)系。
我們來(lái)看上面的圖。說(shuō)白了,ReentrantLock內(nèi)部包含了一個(gè)AQS對(duì)象,也就是
AbstractQueuedSynchronizer類型的對(duì)象。這個(gè)AQS對(duì)象就是ReentrantLock可以實(shí)現(xiàn)加鎖和釋放鎖的關(guān)鍵性的核心組件。
ReentrantLock加鎖和釋放鎖的底層原理
好了,那么現(xiàn)在如果有一個(gè)線程過(guò)來(lái)嘗試用ReentrantLock的lock()方法進(jìn)行加鎖,會(huì)發(fā)生什么事情呢?
很簡(jiǎn)單,這個(gè)AQS對(duì)象內(nèi)部有一個(gè)核心的變量叫做state,是int類型的,代表了加鎖的狀態(tài)。初始狀態(tài)下,這個(gè)state的值是0。
另外,這個(gè)AQS內(nèi)部還有一個(gè)關(guān)鍵變量,用來(lái)記錄當(dāng)前加鎖的是哪個(gè)線程,初始化狀態(tài)下,這個(gè)變量是null。
接著線程1跑過(guò)來(lái)調(diào)用ReentrantLock的lock()方法嘗試進(jìn)行加鎖,這個(gè)加鎖的過(guò)程,直接就是用CAS操作將state值從0變?yōu)?。
如果不知道CAS是啥的,請(qǐng)看上篇文章,《??Java8中的LongAdder類,大大提升CAS性能??!》。
如果之前沒(méi)人加過(guò)鎖,那么state的值肯定是0,此時(shí)線程1就可以加鎖成功。
一旦線程1加鎖成功了之后,就可以設(shè)置當(dāng)前加鎖線程是自己。所以大家看下面的圖,就是線程1跑過(guò)來(lái)加鎖的一個(gè)過(guò)程。
其實(shí)看到這兒,大家應(yīng)該對(duì)所謂的AQS有感覺(jué)了。說(shuō)白了,就是并發(fā)包里的一個(gè)核心組件,里面有state變量、加鎖線程變量等核心的東西,維護(hù)了加鎖狀態(tài)。
你會(huì)發(fā)現(xiàn),ReentrantLock這種東西只是一個(gè)外層的API,內(nèi)核中的鎖機(jī)制實(shí)現(xiàn)都是依賴AQS組件的。
這個(gè)ReentrantLock之所以用Reentrant打頭,意思就是他是一個(gè)可重入鎖。
可重入鎖的意思,就是你可以對(duì)一個(gè)ReentrantLock對(duì)象多次執(zhí)行l(wèi)ock()加鎖和unlock()釋放鎖,也就是可以對(duì)一個(gè)鎖加多次,叫做可重入加鎖。
大家看明白了那個(gè)state變量之后,就知道了如何進(jìn)行可重入加鎖!
其實(shí)每次線程1可重入加鎖一次,會(huì)判斷一下當(dāng)前加鎖線程就是自己,那么他自己就可以可重入多次加鎖,每次加鎖就是把state的值給累加1,別的沒(méi)啥變化。
接著,如果線程1加鎖了之后,線程2跑過(guò)來(lái)加鎖會(huì)怎么樣呢?
我們來(lái)看看鎖的互斥是如何實(shí)現(xiàn)的?線程2跑過(guò)來(lái)一下看到,哎呀!state的值不是0???所以CAS操作將state從0變?yōu)?的過(guò)程會(huì)失敗,因?yàn)閟tate的值當(dāng)前為1,說(shuō)明已經(jīng)有人加鎖了!
接著線程2會(huì)看一下,是不是自己之前加的鎖?。慨?dāng)然不是了,“加鎖線程”這個(gè)變量明確記錄了是線程1占用了這個(gè)鎖,所以線程2此時(shí)就是加鎖失敗。
給大家來(lái)一張圖,一起來(lái)感受一下這個(gè)過(guò)程:
接著,線程2會(huì)將自己放入AQS中的一個(gè)等待隊(duì)列,因?yàn)樽约簢L試加鎖失敗了,此時(shí)就要將自己放入隊(duì)列中來(lái)等待,等待線程1釋放鎖之后,自己就可以重新嘗試加鎖了
所以大家可以看到,AQS是如此的核心!AQS內(nèi)部還有一個(gè)等待隊(duì)列,專門(mén)放那些加鎖失敗的線程!
同樣,給大家來(lái)一張圖,一起感受一下:
接著,線程1在執(zhí)行完自己的業(yè)務(wù)邏輯代碼之后,就會(huì)釋放鎖!他釋放鎖的過(guò)程非常的簡(jiǎn)單,就是將AQS內(nèi)的state變量的值遞減1,如果state值為0,則徹底釋放鎖,會(huì)將“加鎖線程”變量也設(shè)置為null!
整個(gè)過(guò)程,參見(jiàn)下圖:
接下來(lái),會(huì)從等待隊(duì)列的隊(duì)頭喚醒線程2重新嘗試加鎖。
好!線程2現(xiàn)在就重新嘗試加鎖,這時(shí)還是用CAS操作將state從0變?yōu)?,此時(shí)就會(huì)成功,成功之后代表加鎖成功,就會(huì)將state設(shè)置為1。
此外,還要把“加鎖線程”設(shè)置為線程2自己,同時(shí)線程2自己就從等待隊(duì)列中出隊(duì)了。
最后再來(lái)一張圖,大家來(lái)看看這個(gè)過(guò)程。
總結(jié)
OK,本文到這里為止,基本借著ReentrantLock的加鎖和釋放鎖的過(guò)程,給大家講清楚了其底層依賴的AQS的核心原理。
基本上大家把這篇文章看懂,以后再也不會(huì)擔(dān)心面試的時(shí)候被問(wèn)到:談?wù)勀銓?duì)AQS的理解這種問(wèn)題了。
其實(shí)一句話總結(jié)AQS就是一個(gè)并發(fā)包的基礎(chǔ)組件,用來(lái)實(shí)現(xiàn)各種鎖,各種同步組件的。它包含了state變量、加鎖線程、等待隊(duì)列等并發(fā)中的核心組件。