一、寫在前面
這篇文章,我們聊一聊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的源碼的,看內容不少了就放到下一章和大家一起解讀吧。