1. 程式人生 > >大白話聊聊Java併發面試問題之談談你對AQS的理解?【石杉的架構筆記】

大白話聊聊Java併發面試問題之談談你對AQS的理解?【石杉的架構筆記】

歡迎關注個人公眾號:石杉的架構筆記(ID:shishan100)

週一至週五早8點半!精品技術文章準時送上!

一、寫在前面

上一篇文章聊了一下java併發中常用的原子類的原理和Java 8的優化,具體請參見文章:大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?

這篇文章,我們來聊聊面試的時候比較有殺傷力的一個問題:聊聊你對AQS的理解?

之前有同學反饋,去網際網路公司面試,面試官聊到併發時就問到了這個問題。當時那位同學內心估計受到了一萬點傷害。。。

因為首先,很多人還真的連AQS是什麼都不知道,可能聽都沒聽說過。或者有的人聽說過AQS這個名詞,但是可能連具體全稱怎麼拼寫都不知道。

更有甚者,可能會說:AQS?是不是一種思想?我們平時開發怎麼來用AQS?

總體來說,很多同學估計都對AQS有一種雲裡霧裡的感覺,如果用搜索引擎查一下AQS是什麼?看幾篇文章,估計就直接放棄了,因為密密麻麻的文字,實在是看不懂!

所以,基於上述痛點,咱們這篇文章,就用最簡單的大白話配合N多張手繪圖,給大家講清楚AQS到底是什麼?讓各位同學面試被問到這個問題時,不至於不知所措。

二、ReentrantLock和AQS的關係

首先我們來看看,如果用java併發包下的ReentrantLock來加鎖和釋放鎖,是個什麼樣的感覺?

這個基本學過java的同學應該都會吧,畢竟這個是java併發基本API的使用,應該每個人都是學過的,所以我們直接看一下程式碼就好了:


上面那段程式碼應該不難理解吧,無非就是搞一個Lock物件,然後加鎖和釋放鎖。

你這時可能會問,這個跟AQS有啥關係?關係大了去了!因為java併發包下很多API都是基於AQS來實現的加鎖和釋放鎖等功能的,AQS是java併發包的基礎類。

舉個例子,比如說ReentrantLock、ReentrantReadWriteLock底層都是基於AQS來實現的。

那麼AQS的全稱是什麼呢?AbstractQueuedSynchronizer,抽象佇列同步器。給大家畫一個圖先,看一下ReentrantLock和AQS之間的關係。


我們來看上面的圖。說白了,ReentrantLock內部包含了一個AQS物件,也就是AbstractQueuedSynchronizer型別的物件。這個AQS物件就是ReentrantLock可以實現加鎖和釋放鎖的關鍵性的核心元件。

三、ReentrantLock加鎖和釋放鎖的底層原理

好了,那麼現在如果有一個執行緒過來嘗試用ReentrantLock的lock()方法進行加鎖,會發生什麼事情呢?

很簡單,這個AQS物件內部有一個核心的變數叫做state,是int型別的,代表了加鎖的狀態。初始狀態下,這個state的值是0。

另外,這個AQS內部還有一個關鍵變數,用來記錄當前加鎖的是哪個執行緒,初始化狀態下,這個變數是null。


接著執行緒1跑過來呼叫ReentrantLock的lock()方法嘗試進行加鎖,這個加鎖的過程,直接就是用CAS操作將state值從0變為1。

如果不知道CAS是啥的,請看上篇文章,大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?

如果之前沒人加過鎖,那麼state的值肯定是0,此時執行緒1就可以加鎖成功。

一旦執行緒1加鎖成功了之後,就可以設定當前加鎖執行緒是自己。所以大家看下面的圖,就是執行緒1跑過來加鎖的一個過程。


其實看到這兒,大家應該對所謂的AQS有感覺了。說白了,就是併發包裡的一個核心元件,裡面有state變數、加鎖執行緒變數等核心的東西,維護了加鎖狀態。

你會發現,ReentrantLock這種東西只是一個外層的API,核心中的鎖機制實現都是依賴AQS元件的

這個ReentrantLock之所以用Reentrant打頭,意思就是他是一個可重入鎖。

可重入鎖的意思,就是你可以對一個ReentrantLock物件多次執行lock()加鎖和unlock()釋放鎖,也就是可以對一個鎖加多次,叫做可重入加鎖。

大家看明白了那個state變數之後,就知道了如何進行可重入加鎖!

其實每次執行緒1可重入加鎖一次,會判斷一下當前加鎖執行緒就是自己,那麼他自己就可以可重入多次加鎖,每次加鎖就是把state的值給累加1,別的沒啥變化。

接著,如果執行緒1加鎖了之後,執行緒2跑過來加鎖會怎麼樣呢?

我們來看看鎖的互斥是如何實現的?執行緒2跑過來一下看到,哎呀!state的值不是0啊?所以CAS操作將state從0變為1的過程會失敗,因為state的值當前為1,說明已經有人加鎖了!

接著執行緒2會看一下,是不是自己之前加的鎖啊?當然不是了,“加鎖執行緒”這個變數明確記錄了是執行緒1佔用了這個鎖,所以執行緒2此時就是加鎖失敗。

給大家來一張圖,一起來感受一下這個過程:


接著,執行緒2會將自己放入AQS中的一個等待佇列,因為自己嘗試加鎖失敗了,此時就要將自己放入佇列中來等待,等待執行緒1釋放鎖之後,自己就可以重新嘗試加鎖了

所以大家可以看到,AQS是如此的核心!AQS內部還有一個等待佇列,專門放那些加鎖失敗的執行緒!

同樣,給大家來一張圖,一起感受一下:


接著,執行緒1在執行完自己的業務邏輯程式碼之後,就會釋放鎖!他釋放鎖的過程非常的簡單,就是將AQS內的state變數的值遞減1,如果state值為0,則徹底釋放鎖,會將“加鎖執行緒”變數也設定為null!

整個過程,參見下圖:


接下來,會從等待佇列的隊頭喚醒執行緒2重新嘗試加鎖。

好!執行緒2現在就重新嘗試加鎖,這時還是用CAS操作將state從0變為1,此時就會成功,成功之後代表加鎖成功,就會將state設定為1。

此外,還要把“加鎖執行緒”設定為執行緒2自己,同時執行緒2自己就從等待佇列中出隊了。

最後再來一張圖,大家來看看這個過程。



四、總結

OK,本文到這裡為止,基本藉著ReentrantLock的加鎖和釋放鎖的過程,給大家講清楚了其底層依賴的AQS的核心原理。

基本上大家把這篇文章看懂,以後再也不會擔心面試的時候被問到:談談你對AQS的理解這種問題了。

其實一句話總結AQS就是一個併發包的基礎元件,用來實現各種鎖,各種同步元件的。它包含了state變數、加鎖執行緒、等待佇列等併發中的核心元件。

併發系列文章,正在更新中,歡迎關注:
大白話聊聊Java併發面試問題之volatile到底是什麼?
大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?
大白話聊聊Java併發面試問題之談談你對AQS的理解?
大白話聊聊Java併發面試問題之公平鎖與非公平鎖是啥? 敬請期待
大白話聊聊Java併發面試問題之微服務註冊中心的讀寫鎖優化? 敬請期待


END


如有收穫,請幫忙轉發,您的鼓勵是作者最大的動力,謝謝!


一大波微服務、分散式、高併發、高可用的原創系列文章正在路上

歡迎掃描下方二維碼,持續關注:


石杉的架構筆記(id:shishan100)

十餘年BAT架構經驗傾囊相授


推薦閱讀:

1、拜託!面試請不要再問我Spring Cloud底層原理

2、【雙11狂歡的背後】微服務註冊中心如何承載大型系統的千萬級訪問?

3、【效能優化之道】每秒上萬併發下的Spring Cloud引數優化實戰

4、微服務架構如何保障雙11狂歡下的99.99%高可用

5、兄弟,用大白話告訴你小白都能聽懂的Hadoop架構原理

6、大規模叢集下Hadoop NameNode如何承載每秒上千次的高併發訪問

7、【效能優化的祕密】Hadoop如何將TB級大檔案的上傳效能優化上百倍

8、拜託,面試請不要再問我TCC分散式事務的實現原理坑爹呀!

9、【坑爹呀!】最終一致性分散式事務如何保障實際生產中99.99%高可用?

10、拜託,面試請不要再問我Redis分散式鎖的實現原理!

11、【眼前一亮!】看Hadoop底層演算法如何優雅的將大規模叢集效能提升10倍以上?

12、億級流量系統架構之如何支撐百億級資料的儲存與計算

13、億級流量系統架構之如何設計高容錯分散式計算系統

14、億級流量系統架構之如何設計承載百億流量的高效能架構

15、億級流量系統架構之如何設計每秒十萬查詢的高併發架構

16、億級流量系統架構之如何設計全鏈路99.99%高可用架構

17、七張圖徹底講清楚ZooKeeper分散式鎖的實現原理

18、大白話聊聊Java併發面試問題之volatile到底是什麼?

19、大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?