- 概述
隊列同步器AbstractQueuedSynchronizer以下簡稱同步器,是用來構建鎖或者構建其他同步組件的基礎,他使用了一個int成員變量
表示同步狀態,通過內置的FIFO隊列來完成資源獲取線程的排隊工作,并發包的作者(Doug Lea)期望他能夠成為實現大部分同步需求的基礎。
同步器的主要使用方式是繼承,子類通過繼承同步器并實現他抽象方法來管理同步狀態,在抽象方法的實現中免不了對同步狀態的更改,這時就需要使用同步器提供的3個方法(getState(),setState(int newState),cpmpareAndSetState(int expect,int update))來進行操作,因為他們能夠保證改變時安全的。子類推薦被定義為自定義同步組件的靜態內部類,同步器自身沒有實現任何同步解耦,他僅僅是定義了若干同步狀態獲取和釋放的方法來攻同步組件使用,同步器既可以支持獨占式地獲取同步狀態,也可以支持共享方式獲取同步狀態,這樣就可以方便實現不同類型的同步組件(ReentrantLock,ReentrantReadWriteLock和CountDownLatch)
同步器是實現鎖(也可以是任意同步組件)的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義。可以這樣理解二者之間的關系,所示面向使用者的,他定義了使用者和鎖交互接口(比如可以允許兩個線程并行訪問),隱藏了實現了細節;同步器面向的是所得實現者,他簡化了所得實現方式,屏蔽了同步管理,線程的排隊,等待與喚醒等底層操作,鎖和同步器很好地隔離了使用者和實現這所關注的領域。
-
隊列同步器的接口與示例
同步器的設計是基于模板方法模式的,也就是說,使用這需要繼承同步器并重寫制定的方法,隨后將同步器組合在自定義的同步組件的實現中,并調用同步器提供的模板方法,而這寫模板方法將會調用使用者重寫的方法。
重寫同步器自定的方法時,需要使用同步器提供的如下3個方法來訪問或者修改同步狀態,- getState(): 獲取當前同步狀態
- setState(int newState) : 設置當前同步狀態
- compareAndSetState(int except,int update): 使用CAS設置當前狀態,該方法能夠保證狀態設置的原子性。
同步器可以重寫的方法如下表所示:
方法名稱 | 描述 |
---|---|
priotected boolean tryAcquire(int arg) | 獨占式獲取同步狀態,實現該方法需要查詢當前狀態并判斷同步狀態是否符合預期,然后進行CAS設置同步狀態 |
protected boolean tryRelease(int arg) | 獨占式釋放同步狀態,等待獲取同步狀態的線程將會有機會獲取同步狀態 |
priotected int tryAcquireShare(int arg) | 共享獲取同步狀態,返回大于等于0的值表示獲取成功,反之獲取失敗 |
protected boolean tryRelease(int arg) | 共享式釋放鎖 |
protected BooleaninHeldExclusively() | 當前線程同步器是否在獨占模式下被線程占用,一般該方法表示是否被當前線程獨占 |
實現自定義同步組件時,將會調用同步提供模板方法,這些部分模板方法描述如下:
方法名稱 | 描述 |
---|---|
void acquire(int arg) | 獨占式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列中等待,該方法將會調用重寫tryAcquire(int arg) 方法 |
void acquireInterruotibly(int arg) | 與acquire(int arg)相同,但是該方法響應中斷,當前線程未獲取到同步狀態二進入同步隊列中, |
如果當前線程被中斷,則該方法會拋出InterruptedException比返回
boolean tryAcquireNanos(int arg)|在該acquireInterruptibly(int arg)基礎上增加了超時限制,如果當時線程在超時時間內沒有獲取到獲取到同步狀態,那么將會返回false,如果獲取到了就返回true
boolean acquireShare(int arg)|共享式地獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨占鎖獲取的主要區別是在同一時刻多個線程獲取同步狀態
void acquireShareInterruptibly(int arg)|與acquireShare(int arg)相同,該方法響應中斷
boolean tryAcquireShareNanos(int arg)|在acquireShareInterruptibly(int arg)的基礎上增加了超時限制
boolean release(int arg)|獨占式的釋放同步狀態,該方法在釋放同步狀態之后,將同步隊列中第一個節點包含的線程喚醒
boolean releaseShared(int arg)|共享式釋放同步狀態
Collection<Thread> getQueuedThread()|獲取等待在同步隊列的線程集合
同步器提供的模板方法基本上分為3類:獨占式獲取與釋放同步狀態,共享方式獲取和釋放同步狀態和查詢同步隊列中等待線程的情況。自定義同步組件將使用同步器提供的模板方法來實現自己的同步語義。
只有掌握了同步器的工作原理才能更加深入理解并發包中的其它并發組件,所以下面通過一個獨占鎖的實例來深入理解一下同步器的工作原理。
顧名思義,獨占鎖就是同一時刻只能一個線程獲取到鎖,而其他獲取鎖的線程只能處于同步隊列中進行等待,只有獲取鎖的鎖釋放了鎖,后續的線程才能獲取鎖,如下面的代碼清單
class Mutex implements Lock{
//靜態內部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer{
//是否處于占用狀態
protected boolean idHeldExclusively(){
return getState() == 1;
}
//當狀態為0的時候獲取鎖
public boolean tryAcquire(int acquires){
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//釋放鎖,將狀態設置為0
public boolean tryAcquire(int release){
if(getState() == 0) throw new IllegaMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//返回一個Condition,每個condition都包含了一個condition隊列
Condition newCondition(){
return new ConditionObject();
}
}
private final Sync sync = new Sync();
public void lock(){
sync.acquire(1);
}
public boolean tryLock(){
return sync.tryAcquire(1);
}
public void unLock(){
return sync.release(1);
}
public Condition newCondition(){
return sync.newCondition();
}
public boolean isLocked(){
return sync.isHeldExclusiveLy();
}
public boolean hasQueueThreads(){
return sync.hasQueueThreads();
}
public void lockInterruptibly() throws InterruptedException(){
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout,TieUnit unit) throws InterruptedException{
return sync.tryAcquireNanos(1,unit.toNanos(timeout));
}
}
上述實例中,獨占鎖Mutex是一個自定義同步組件,他在同意時刻只允許一個線程占有鎖,Mutex中的定義了一個靜態內部類,該內部類繼承了同步器并實現獨占式獲取和釋放同步狀態,在tryAcquire(int arg)方法中,如果經過CAS設置成功(同步狀態設置為1),則代表獲取了同步狀態,而在tryRelease(int release)方法中只是將同步狀態重置為0.用戶使用Mutex時并不會直接和內部同步器直接打交道,而是調用Mutex提供的方法,在Mutex中,以獲取鎖的lock()方法為例,只需要在方法實現中調用同步器的模板方法acquire(int args)即可,當前線程調用該方法獲取同步狀態失敗后會被加入到同步隊列中等待,這就大大降低了實現一個可靠自定義同步組件的門檻。
- 隊列同步器的實現方式
接下來將從實現角度分析同步器是如何完成線程同步的,主要包括:同步隊列,獨占式同步狀態的獲取和釋放,共享式同步狀態的獲取和釋放以及超時獲取同步狀態等同步器的核心數據結構與模板方法。
1.同步隊列
同步器依賴內部的同步的隊列(一個FIFO的雙向隊列)來完成同步狀態的管理,當前線程獲取同步狀態失敗時,同步器會將當前線程以及等待狀態等信息構建一個節點并將其加入到同步隊列,同時會堵塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,將使其再次嘗試獲取同步狀態。
同步隊列中的節點用來保存獲取同步狀態失敗的線程引用,等待狀態以及前驅和后續節點,節點的屬性類型與名稱以及描述如下:
屬性類型與名稱 | 描述 |
---|---|
int waitStatus | 等待狀態,包含如下狀態:1).CANNELLED,值為1,由于再同步隊列中等待的線程等待超時或者被中斷,需要從同步隊列中取消等待,節點進入改狀態將不會發生變化 2).SIGNAL,值為-1,后繼節點的想成處于等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知后繼節點,使后繼節點的線程得以運行 3).CONDITION,值為-2,節點在等待隊列中,節點線程等待在Condition上,當其他線程對Condition調用了signal()方法后,該節點將會從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取。 4).PROPAGATE 值為-3,表示下一次的共享式同步狀態獲取將會無條件傳播下去 5.INITIAL 值為0,初始狀態 |
Node pre | 前驅節點,當節點加入到同步隊列時被設置(尾部添加) |
Node next | 后續節點 |
Node nextWaiter | 等待隊列中的后續節點,如果當前節點是共享的,那么這個字段將會是一個SHARED常量,也就是節點類型(獨占和共享)和等待隊列中的后續節點共用一個字段 |
Thread thread | 獲取同步狀態的線程 |
節點是構成同步隊列的基礎,同步器擁有首節點(head)和節點(tail),沒有成功獲取同步狀態的線程將會成為節點加入該隊列的尾部,同步隊列的基本結構如下:
在上圖中,同步器包含了兩個節點類型的引用,一個指向頭節點,而另一個指向尾節點,試想一下,當一個線程成功地獲取同步狀態,其他線程將無法獲取到同步狀態,轉而被構造成節點并加入到同步隊列,而這個加入隊列的過程中必須保證線程安全,因此同步器提供了一個基于CAS的設置尾節點的方法;compareAndSetTail(Node except,Node update),他需要傳遞當前線程‘認為’的尾節點和當前節點,只有設置成功了,當前節點才正式與之前的為節點建立關聯。同步器將節點加入到同步隊列的過程如下圖所示:
同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點,首節點的線程在釋放同步狀態時,將會喚醒后繼節點,而后繼節點將會在獲取同步狀態成功是將自己設置為首節點。
設置首節點是通過獲取同步狀態成功的線程來完成的,由于只有一個線程能夠成功獲取同步狀態,因此設置頭節點的方法設置頭節點的方法并不需要使用CAS來保證,他只需要將首節點設置為原來首節點的后繼節點并斷開原首節點的next引用就可以。
2.獨占式同步狀態獲取和釋放
通過調用同步器的acquire(int arg)方法可以獲取同步狀態,該方法對中斷不敏感,也就是由于線程的獲取同步狀態失敗后進入同步狀態中,后繼對線程進行中斷操作,線程不會從同步隊列中移出,該方法代碼如下:
public final void acquire(int arg){
if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}
上述代碼主要完成同步狀態的獲取,節點構造,加入同步隊列以及在同步隊列中自旋等待的相關工作,其主要邏輯是:首先調用自定義的同步器實現tryAcquire(int arg)方法,該方法保證線程安全的獲取同步狀態,如果同步狀態獲取失敗,則構造同步節點(獨占式Node EXCLUSIVE,同一時刻只能有一個線程成功獲取狀態)并通過addWaiter(Nod).