大家好,我是小黑,一個在網際網路苟且偷生的農民工。
在Java并發程式設計中,經常會用到鎖,除了Synchronized這個JDK關鍵字以外,還有Lock接口下面的各種鎖實作,如重入鎖ReentrantLock,還有讀寫鎖ReadWriteLock等,他們在實作鎖的過程中都是依賴與AQS來完成核心的加解鎖邏輯的。那麼AQS具體是什麼呢?
提供一個架構,用于實作依賴先進先出(FIFO)等待隊列的阻塞鎖和相關同步器(信号量,事件等)。 該類被設計為大多數類型的同步器的有用依據,這些同步器依賴于單個原子int值來表示狀态。 子類必須定義改變此狀态的受保護方法,以及根據該對象被擷取或釋放來定義該狀态的含義。 給定這些,這個類中的其他方法執行所有排隊和阻塞機制。 子類可以保持其他狀态字段,但隻以原子方式更新int使用方法操縱值getState() , setState(int)和compareAndSetState(int, int)被跟蹤相對于同步。
上述内容來自JDK官方文檔。
簡單來說,AQS是一個先進先出(FIFO)的等待隊列,主要用在一些線程同步場景,需要通過一個int類型的值來表示同步狀态。提供了排隊和阻塞機制。

從類圖可以看出,在ReentrantLock中定義了AQS的子類Sync,可以通過Sync實作對于可重入鎖的加鎖,解鎖。
AQS通過int類型的狀态state來表示同步狀态。
AQS中主要提供的方法:
acquire(int) 獨占方式擷取鎖
acquireShared(int) 共享方式擷取鎖
release(int) 獨占方式釋放鎖
releaseShared(int) 共享方式釋放鎖
獨占鎖和共享鎖
關于獨占鎖和共享鎖先給大家普及一下這個概念。
獨占鎖指該鎖隻能同時被一個線程持有;
共享鎖指該鎖可以被多個線程同時持有。
舉個生活中的例子,比如我們使用打車軟體打車,獨占鎖就好比我們打快車或者專車,一輛車隻能讓一個客戶打到,不能兩個客戶同時打到一輛車;共享鎖就好比打拼車,可以有多個客戶一起打到同一輛車。
我們簡單通過一張圖先來了解下AQS的内部結構。其實就是有一個隊列,這個隊列的頭結點head代表目前正在持有鎖的線程,後續的其他節點代表目前正在等待的線程。
接下來我們通過源碼來看看AQS的加鎖和解鎖過程。先來看看獨占鎖是如何進行加解鎖的。
可以看到在ReentrantLock的lock方法中,直接調用了sync這個AQS子類的lock方法。
在擷取鎖時,基本可以分為3步:
嘗試擷取,如果成功則傳回,如果失敗,執行下一步;
将目前線程放入等待隊列尾部;
标記前面等待的線程執行完之後喚醒目前線程。
在整個加鎖過程可以通過下圖更清晰的了解。
同樣解鎖時也是直接調用AQS子類sync的release方法。
解鎖過程如下:
先嘗試解鎖,解鎖失敗則直接傳回false。(理論上不會解鎖失敗,因為正在執行解鎖的線程一定是持有鎖的線程)
解鎖成功之後,如果有head節點并且狀态不是0,代表有線程被阻塞等待,則喚醒下一個等待的線程。
為了實作共享鎖,AQS中專門有一套和排他鎖不同的實作,我們來看一下源碼具體是怎麼做的。
tryAcquireShared嘗試擷取共享許可,本方法需要在子類中進行實作。不同的實作類實作方式不一樣。
下面的代碼是ReentrentReadWriteLock中的實作。
本方法可以總結為三步:
如果有寫線程獨占,則失敗,傳回-1
沒有寫線程或者目前線程就是寫線程重入,則判斷是否讀線程阻塞,如果不用阻塞則CAS将已使用讀鎖個數+1
如果第2步失敗,失敗原因可能是讀線程應該阻塞,或者讀鎖達到上限,或者CAS失敗,則調用fullTryAcquireShared方法。
AQS是很多并發場景下同步控制的基石,其中的實作相對要複雜很多,還需要多看多琢磨才能完全了解。本文也是和大家做一個初探,給大家展示了核心的代碼邏輯,希望能有所幫助。
好的,本期内容就到這裡,我們下期見;關注公衆号【小黑說Java】更多幹貨。