歡迎關注我的公衆號:石杉的架構筆記(ID:shishan100)
java
週一至週五早8點半!精品技術文章準時送上!面試
上一篇文章聊了一下java併發中經常使用的原子類的原理和Java 8的優化,具體請參見文章:大白話聊聊Java併發面試問題之Java 8如何優化CAS性能?。算法
這篇文章,咱們來聊聊面試的時候比較有殺傷力的一個問題:聊聊你對AQS的理解?性能優化
以前有同窗反饋,去互聯網公司面試,面試官聊到併發時就問到了這個問題。當時那位同窗心裏估計受到了一萬點傷害。。。架構
由於首先,不少人還真的連AQS是什麼都不知道,可能聽都沒據說過。或者有的人據說過AQS這個名詞,可是可能連具體全稱怎麼拼寫都不知道。併發
更有甚者,可能會說:AQS?是否是一種思想?咱們平時開發怎麼來用AQS?分佈式
整體來講,不少同窗估計都對AQS有一種雲裏霧裏的感受,若是用搜索引擎查一下AQS是什麼?看幾篇文章,估計就直接放棄了,由於密密麻麻的文字,實在是看不懂!微服務
因此,基於上述痛點,我們這篇文章,就用最簡單的大白話配合N多張手繪圖,給你們講清楚AQS究竟是什麼?讓各位同窗面試被問到這個問題時,不至於不知所措。高併發
首先咱們來看看,若是用java併發包下的ReentrantLock來加鎖和釋放鎖,是個什麼樣的感受?oop
這個基本學過java的同窗應該都會吧,畢竟這個是java併發基本API的使用,應該每一個人都是學過的,因此咱們直接看一下代碼就行了:
上面那段代碼應該不難理解吧,無非就是搞一個Lock對象,而後加鎖和釋放鎖。
你這時可能會問,這個跟AQS有啥關係?關係大了去了!由於java併發包下不少API都是基於AQS來實現的加鎖和釋放鎖等功能的,AQS是java併發包的基礎類。
舉個例子,好比說ReentrantLock、ReentrantReadWriteLock底層都是基於AQS來實現的。
那麼AQS的全稱是什麼呢?AbstractQueuedSynchronizer,抽象隊列同步器。給你們畫一個圖先,看一下ReentrantLock和AQS之間的關係。
咱們來看上面的圖。說白了,ReentrantLock內部包含了一個AQS對象,也就是AbstractQueuedSynchronizer類型的對象。這個AQS對象就是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併發面試問題之微服務註冊中心的讀寫鎖優化? 敬請期待
若有收穫,請幫忙轉發,您的鼓勵是做者最大的動力,謝謝!
一大波微服務、分佈式、高併發、高可用的原創系列文章正在路上
歡迎掃描下方二維碼,持續關注:
石杉的架構筆記(id:shishan100)
十餘年BAT架構經驗傾囊相授
推薦閱讀:二、【雙11狂歡的背後】微服務註冊中心如何承載大型系統的千萬級訪問?
三、【性能優化之道】每秒上萬併發下的Spring Cloud參數優化實戰
六、大規模集羣下Hadoop NameNode如何承載每秒上千次的高併發訪問
七、【性能優化的祕密】Hadoop如何將TB級大文件的上傳性能優化上百倍
八、拜託,面試請不要再問我TCC分佈式事務的實現原理坑爹呀!
九、【坑爹呀!】最終一致性分佈式事務如何保障實際生產中99.99%高可用?
十一、【眼前一亮!】看Hadoop底層算法如何優雅的將大規模集羣性能提高10倍以上?
1六、億級流量系統架構之如何設計全鏈路99.99%高可用架構