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