標籤:style java 使用 io strong for ar 2014 art
Semaphore是一個計數的訊號量。從概念上來說,訊號量維持一組許可(permits)。acquire方法在必須的時候都會堵塞直到有一個許可可用,然後就會拿走這個許可。release方法加入一個許可,會有可能釋放一個堵塞中的擷取者(acquirer)。然而,Semaphore沒有使用真實的許可對象,僅僅是保持一個可用計數而且採取對應的行為。
訊號量一般用於限制能夠訪問一些(物理上或者邏輯上)的資源的並發線程數。
訊號量初始化為1的時候,意味著它最多僅僅有一個同意可用,這樣就能作為相互排斥獨佔鎖使用。這樣的很多其它地被稱為二進位訊號量(binary semaphore),由於它僅僅有兩個狀態:一個許可可用,或者0個許可可用。當使用這樣的方式的時候,二進位訊號量就有這樣的屬性(不像大部分鎖的實現):鎖能夠被擁有者(就如訊號量沒有擁有者的概念)之外的另外線程釋放。這樣的屬性在某些特殊的上下文中非常實用,比如死結恢複。
類的建構函式可選地接受一個fairness參數。當設為false的時候,該類就不會保證線程擷取許可的順序。特別地,插隊是同意的,也就是說,線程調用acquire方法能夠在另外的等待線程之前分配許可————邏輯上新線程會把自己放在等待線程隊列頭。當fairness設為true,訊號量就保證調用acquire方法的線程會以它們調用方法的處理順序來擷取許可(FIFO)。注意FIFO的順序決定特指在這些方法的內部運行點。因此,有可能一條線程在另外一條線程之前調用了acquire方法,但實際順序會在另外一條線程之後,相同也適用於函數返回的先後順序。相同要注意非逾時版本號碼tryAcquire方法不會遵循fairness設定,會立即擷取不論什麼可用的許可。
一般來說,訊號量用來控制資源訪問的話,就應該被初始化為公平(fairness設為true),這樣能夠確保沒有線程會在訪問資源的時候餓壞。當使用在其它同步控制的情況下使用訊號量,非公平的輸送量優勢一般優於公平的考慮。
事實上整體上來看,fairness參數以及狀態量的概念非常接近AQS(AbstractQueuedSynchronizer)提供的功能,因此大家也應該猜到Semaphore的內部實現也是通過一個繼承AQS的內部類實現介面功能。接下來我們細緻看看內部的實現。
詳細實現
先來看看Semaphore的建構函式:
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); } 能夠看到建構函式與ReentrantLock實作類別似,都是依照fair參數分配建立不同的鎖類,再來看看Semaphore的acquire和release的介面實現
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void release() { sync.releaseShared(1); } 能夠看到acquire和release的實現都是調用內部類Sync的方法實現,當然了,這些方法也就是AQS提供出來的擷取和釋放共用鎖定介面。接下來看看整個實現裡最基本的內部類Sync的相關實現:
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1192457210091910933L; Sync(int permits) { setState(permits); } final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } } //省略一些次要方法 } /** * 非公平版本號碼 */ static final class NonfairSync extends Sync { private static final long serialVersionUID = -2694183684443567898L; NonfairSync(int permits) { super(permits); } protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } } /** * 公平版本號碼 */ static final class FairSync extends Sync { private static final long serialVersionUID = 2014338818796000944L; FairSync(int permits) { super(permits); } protected int tryAcquireShared(int acquires) { for (;;) { if (hasQueuedPredecessors()) return -1; int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } } 為了方便瞭解主要邏輯,Sync類省略掉一些次要的方法。非公平版本號碼NonfairSync類和公平版本號碼FairSync類都繼承於Sync類,Sync類繼承於AQS類,NonfairSync和FairSync類都有相同的tryReleaseShared實現,僅僅只是在tryAcquireShared實現上有略微不同。
先來看看非公平類NonfairSync實現。tryAcquireShared調用的是Sync類的nonfairTryAcquireShared方法,方法的實現相當簡單,僅僅是在迴圈內推斷當前鎖狀態值減去請求值acquires後,假設remaining < 0(則表示此次acquire失敗,直接返回負值remaining就可以)或者remaining >=0 時,compareAndSetState成功(表示此次acquire成功,直接返回大於等於0的remaining就可以),假設CAS失敗,則繼續迴圈重試,直到當中一種情況發生。
再來看看公平類FairSync的實現。tryAcquireShared直接被重寫,與非公平類版本號碼對照,添加了hasQueuedPredecessors的推斷,該方法在AQS中表示是否有結點在當前的等待隊列前排在自己前面,假設返回true,則表示當前線程須要進入等待隊列,直接返回-1表示acquire失敗。
tryReleaseShared的實現也非常easy,也是一個迴圈裡不斷CAS把鎖狀態添加請求的releases就可以。
Semaphore還有其他一些輔助方法,事實上現也都是簡單地調用內部類Sync的方法,這裡便不再贅述。
總結
整體來看,AQS的鎖狀態值就等於Semaphore的許可量,acquire的實現就是把當前鎖狀態值,也就是許可量減去相應值,release的實現就是把鎖狀態值添加相應值就可以。整個實現結構和ReentrantLock類似,但沒有了重入的邏輯,並且實現更是相對簡單,理解起來應該沒有難度。
Semaphore實現Andoird版原始碼剖析