前言
AQS是AbstractQueuedSynchronizer的簡稱,提供了一種實現阻塞鎖和一系列依賴FIFO等待隊列的同步器的框架,如圖所示:在併發包中AQS有着非常重要的地位,本文就來深扒AQS的實現機制。
正文
同步器使用
在這裏以ReentrantLock爲例,同步器的主要使用方式是繼承並實現抽象方法管理同步狀態,可以看下源碼:
同步器提供了三個用來管理同步狀態的方法,分別是
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS的子類通常定義爲同步組件的靜態內部類,如同ReentrantLock中所示,因爲同步器本身沒有實現任何接口,僅僅定義了同步狀態的操作方法供組件使用。此外,同步器支持獨佔氏和共享氏兩種獲取資源的狀態,基於此就可以實現自定義的組件(例如ReentrantLock、ReentrantReadWriteLock、CountDownLatch),這裏給大家解釋一下獨佔氏與共享氏,獨佔氏指的是某個資源特定時間只能有一個線程獲取鎖,其他獲取這個鎖的線程只能處理同步隊列中等待,只有當前線程釋放了鎖,後續線程採用獲取鎖。共享氏:在同一個時刻可以有多個線程獲取同步狀態,比如讀寫鎖
同步器可重寫方法
同步器的設計是基於模版方法模式,使用者需要重寫指定的方法,然後將同步器組合在自己的組件中使用,同步器提供了以下可重寫的方法,在調用的過程中會使用重寫的方法
- boolean tryAcquire(int arg): 獨佔式的獲取同步狀態,實現該方法需要先獲取並判斷同步狀態是否符合預期,再通過CAS設置同步狀態。(即使用compareAndSetState(expect, update)方法)。可用於實現Lock接口中的tryLock()方法。=
- boolean tryRelease(int arg): 獨佔式的釋放同步狀態,等待獲取同步狀態的線程將有機會獲取同步狀態,實現Lock的unlock()。
- int tryAcquireShared(int arg): 共享式的獲取同步狀態,返回大於等於0的值表示獲取成功,否則獲取失敗。
- boolean tryReleaseShared(int arg): 共享式的釋放同步狀態
- boolean isHeldExclusively(): 用於獲取同步器是否在獨佔模式下被線程佔用,一般用來表示是否被當前線程所獨佔。
同步器的模版方法
實現自定義的同步組件,需要調用同步器中模板方法,而模板方法內部則調用的是同步器的可重寫方法
- void acquire(int arg): 獨佔式的獲取同步狀態,會調用tryAcquire()重寫方法。如果當前線程獲取同步狀態成功,則直接返回;否則,進入同步隊列等待。
- void acquireInterruptibly(int arg): 與acquire()相同,但可以響應中斷。如果當前線程獲取同步狀態失敗,進入同步隊列,直到獲取成功或響應中斷。如果響應中斷,會拋出中斷異常(InterruptException)。
- boolean tryAcquireNanos(int arg, long nanosTimeout): 在acquireInterruptibly()的基礎上增加了等待時間,如果等待超時,則返回false;如果在等待的時間內,成功獲取則返回true。
- boolean release(int arg): 獨佔式的釋放同步狀態,並在釋放同步狀態之後,喚醒同步隊列中第一個節點包含的線程。
- void acquireShared(int arg): 共享式的獲取同步鎖,與獨佔式獲取的主要區別:同一時刻可以有多個線程獲取到同步狀態。
- void acquireSharedInterruptibly(int arg): 與acquireShared()相同,但可以響應中斷。
- boolean tryAcquireSharedNanos(int arg, long nanosTimeout): 在acquireSharedInterruptibly()的基礎上增加了等待時間。
- boolean releaseShared(int arg): 共享式的釋放同步狀態
- Collection getQueuedThreads(): 獲取在同步隊列中等待的線程集合。
自定義組件的例子
這個例子是實現的一個獨佔氏的鎖,對於重寫方法、模版方法、同步器的組合都非常明白的定義了出來
public class Mutex implements Lock {
/**
* 隊列同步器的extend
*/
private static class Sync extends AbstractQueuedSynchronizer{
/**
* 是否處於佔用狀態
* @return
*/
@Override
protected boolean isHeldExclusively(){
return getState()==1;
}
/**
* 狀態爲0時獲取鎖
* @param acquires
* @return
*/
@Override
public boolean tryAcquire(int acquires){
if (compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int release){
if (getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition(){
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
隊列同步器的實現
前面在介紹資源的獲取方式的時候提到過同步隊列,沒有獲取到鎖的線程將會同步隊列中等待。那麼這個同步隊列是如何實現的呢?
在同步器的內部維護了一個FIFO的同步隊列,同步器內部定義了Node節點,節點包含了線程需要獲取資源的各種信息,同步隊列就是由這些節點鏈接而成的,並且指明瞭頭/尾節點。
同步隊列如圖所示(來自網絡):千萬別說這是個鏈表(因爲我想過,哈哈)這可是滿足FIFO原則的
最後我們看一下線程獲取獨佔氏獲取鎖的流程
節點加入隊列尾部的源碼如下:
嘗試循環添加節點
最後節點添加成功後,就進入自旋狀態,不斷的判斷條件,滿足條件就獲取同步狀態