J.U.C|帶你走進AQS的內心世界

一、寫在前面

這篇文章,我們聊一聊Java併發中的核武器, AQS底層實現。

不管是工作三四年、還是五六年的在工作或者面試中涉及到併發的是時候總是繞不過AQS這個詞。

首先,確實還有很多人連AQS是什麼都不知道,甚至有的竟不知其爲何物。或者有的聽說過其名,但怎麼拼寫的都忘記了。

總的來說確實有很多同學對AQS總有一種雲裏霧裏的感覺,在搜索引擎中搜下AQS看個幾篇文章,估計對其還是醉醺醺的。

所以根據上面的難點,這篇我們使用由簡入難的方式,讓你一次搞定這Java併發中這個核武器AQS。

二、ReentrantLock 和 AQS 的關係

首先我們以大家最熟悉的方式帶你進入這個核武器庫的大門,Java 併發包下的 ReentrantLock大家肯定很熟悉了。

基本上學過Java 的都知道ReentrantLock,下面我就不多說了直接上一段代碼。

ReentrantLock lock = new ReentrantLock();
try {
    lock.lock(); // 加鎖

    // 業務邏輯代碼

} finally {
    lock.unlock(); // 釋放鎖
}

這段代碼大家應該很熟悉了,無非就是獲取一把鎖,加鎖和釋放鎖的過程。

有同學就問了這和AQS有毛關係呀!彆着急,告訴你關係大着去了。在Java併發包中很多鎖都是通過AQS來實現加鎖和釋放鎖的過程的,AQS就是併發包基礎。

例如:ReentrantLock、ReentrantReadWriteLock 底層都是通過AQS來實現的。

那麼AQS到底爲何物尼?別急,我們一步一來揭開其神祕的面紗。

AQS 的全稱 AbstractQueuedSynchronizers抽象隊列同步器,給大家畫三張圖來說明其在Java 併發包的地位、 長啥樣、和ReentrantLock 的關係。

通過此類圖可以彰顯出了AQS的地位、上層鎖實現基本都是通過其底層來實現的。

有沒有被忽悠的感覺?你沒看錯AQS就長這個鳥樣。說白了其內部就是包含了三個組件

  • state 資源狀態
  • exclusiveOwnerThread 持有資源的線程
  • CLH 同步等待隊列。


在看這張圖現在明白ReentrantLock 和 AQS 的關係了吧!大白話說就是ReentrantLock其內部包含一個AQS對象(內部類),AQS就是ReentrantLock可以獲取和釋放鎖實現的核心部件。

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

好了! 經過上面的介紹估計大家已經對AQS混了個臉熟,下面我們就來說說這一段代碼。

ReentrantLock lock = new ReentrantLock();
try {
    lock.lock(); // 加鎖

    // 業務邏輯代碼

} finally {
    lock.unlock(); // 釋放鎖
}

這段代碼加鎖和釋放鎖到底會發生什麼故事尼?

很簡單在AQS 內部有一個核心變量 (volatile)state 變量其代表了加鎖的狀態,初始值爲0。

另外一個重要的關鍵 OwnerThread 持有鎖的線程,默認值爲null


接着線程1過來通過lock.lock()方式獲取鎖,獲取鎖的過程就是通過CAS操作volatile 變量state 將其值從0變爲1。

如果之前沒有人獲取鎖,那麼state的值肯定爲0,此時線程1加鎖成功將state = 1。

線程1加鎖成功後還有一步重要的操作,就是將OwnerThread 設置成爲自己。如下圖線程1加鎖過程。


其實到這大家應該對AQS有個大概認識了,說白了就是併發包下面的一個核心組件,其內部維持state變量、線程變量等核心的東西,來實現加鎖和釋放鎖的過程。

大家有沒有意識到不管是ReentrantLock還是ReentrantReadWriteLock 等爲什麼都是Reentrant 開頭尼?

從單詞本身意思也能看出,Reentrant 可重入的意思 ,也就說其是一個可重入鎖。

可重入鎖?

就是你可以對一個 ReentrantLock 進行多次的lock() 和 unlock() 操作,也就是可以對一個鎖加多次,叫做可重入鎖。 來一段代碼直觀感受下。

ReentrantLock lock = new ReentrantLock();
try {
    lock.lock(); // 加鎖1

    // 業務邏輯代碼
    lock.lock() // 加鎖2
    
    // 業務邏輯代碼
    
    lock.lock() // 加鎖3

} finally {
    lock.unlock(); // 釋放鎖3
    lock.unlock(); // 釋放鎖2
    lock.unlock(); // 釋放鎖1
}

注意:釋放鎖是由內到外依次釋放的,不可缺少。

問題又來了?ReentrantLock 內部又是如何來實現的尼?

說白了!還是我們AQS這個核心組件幫我實現的,很 easy~ 上述兩個核心變量 state 和 OwnerThread 還記得吧!

重入就是判斷當前鎖是不是自己加上的,如果是,就代表自己可以再次上鎖,每重入一次就是將state值加1。就是這麼簡單啦!!!

說完了可重入我們再來看看鎖的互斥又是如何實現的尼?

此時線程2也跑過來想加鎖,CAS操作嘗試將 state 從0 變成 1, 哎呀!糟糕state已經不是0了,說明此鎖已經被別人拿到了。

接着線程2想??? 這個鎖是不是我以前加上的,瞅瞅 OwnerThread=線程1 哎! 明顯不是自己上的 ,悲催加鎖失敗了~~~。來張圖記錄下線程2的悲苦經歷。

可是線程2加鎖失敗將何去何從尼?

線程2:想,要是有個地方讓我休息下,等線程1釋放鎖後通知我下再來從新嘗試上鎖就好了。

這時我們的核心部件AQS又登場了!

AQS: OK! 好吧!那我就給你提供一個落腳地吧(CLH)進去待着吧!一會讓線程1叫你。

線程2: 屁顛屁顛的就去等待區小憩一會去了。同樣來張圖記錄下線程2高興樣。


此時線程1業務執行完了,開始釋放鎖

  • 將state值改爲0
  • 將OwnerThread 設爲null
  • 通知線程2鎖我已經用完了,該你登場了

線程2一聽,樂壞了!立馬開始嘗試獲取鎖,CAS 嘗試將 state 值設爲 1 ,如果成功將OwnerThread設爲自己 線程2。
此時線程2成功獲取到了鎖,再來張圖瞅瞅。


四、總結

Ok !到這藉着Reentrantkock 的加鎖和釋放鎖的過程給大家講解了一下AQS工作原理。

用一句話總結下:AQS就是Java併發包下的一個基礎組件,用來實現各種鎖和同步組件的,其核心分爲三個組件。

  • Volatile state 變量
  • OwnerThread 加鎖線程
  • CLH 同步等待隊列

等併發核心組件。

本來想一起解讀下AQS的源碼的,看內容不少了就放到下一章和大家一起解讀吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章