Semaphore源碼解析以及與CountDownLatch的對比

寫在前面

信號量,源碼中的註釋是這樣寫的 :

A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each {@link #acquire} blocks if necessary until a permit is available, and then takes it. Each {@link #release} adds a permit, potentially releasing a blocking acquirer.

其大致的意思是:它是一個計數信號量。概念上它是一個持有很多“許可”的信號量。每一個acquire方法都會被阻塞,直到有可用的許可,而每一個release方法都將產生一個“許可”,進而釋放掉阻塞的acquire.

總體上,semaphore可以表達成以下這樣的形狀:
信號量模型上圖爲信號量的模型,許可池就是信號量對象初始時設置的“許可”數量,然後每個線程要想做事情,就需要先獲取“許可”,如果獲取到許可,池裏面的“許可”就減一,如果事情做完了,則釋放許可(放回),沒有獲取到許可的線程則需要等待。從以上內容我們可以看到其最重要的兩個方法就是acquire和release,接下來我們看一下重點的代碼實現。

總體結構

abstract static class Sync extends AbstractQueuedSynchronizer {
    Sync(int permits) { // 初始化許可池
        setState(permits);
    }
    final int getPermits() {
        return getState();
    }
 //非公平策略的tryAcquireShared
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {  //自旋+cas
            int available = getState();  //獲取當前狀態,也就是可用的許可
            int remaining = available - acquires; //減去本次獲取的許可
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
            return remaining;  //如果剩餘許可小於0,則返回複數表示獲取共享鎖失敗,否則通過cas改變剩餘許可
         }
    }
 //釋放共享鎖
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {//自旋+cas
            int current = getState();//獲取當前狀態,也就是可用的許可
            int next = current + releases;  //加上本次釋放的許可
            if (next < current) // overflow
               throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next)) //同樣通過cas改變剩餘許可
               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;
        }
    }
}

以上代碼我只貼了nonfairTryAcquireShared和tryReleaseShared,其它方法類似,這裏主要是想說明,semaphore利用AQS做了什麼。
從構造方法來看,這裏會初始化state,給到一個初始化許可池(這裏的實現與CountDownLatch非常相似,詳情見:CountDownLatch源碼解析,都是先初始state爲一個整整數,然後後續的操作都是圍繞這個state來進行。

核心方法

首先看acquire方法:

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

這裏可以看出,acquire其實就是調用了同步器的acquireSharedInterruptibly方法,而這個方法的實現是通過子類對具體的tryAcquireShared的實現來體現差異的,這裏自然就聯想到了我們這裏的tryAcquireShared方法實現。邏輯非常簡單,就是從當前的state減去本次獲取的值,然後cas更新state值,如果減去後新的state小於0,則代表獲取共享資源失敗,則當前線程會不但嘗試獲取或者睡眠(見AQS實現)。
同理,release也是同樣類似的邏輯:

public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}

釋放鎖調用了releaseShared方法,而該方法最後會調用這裏的實現tryReleaseShared方法,這裏的tryReleaseShared方法同樣比較簡單,就是正常的累加許可,這裏不再累述。

總結

其實看semaphore的代碼更應該結合CountDownLatch的代碼來看,這兩個線程協調器的實現非常類似,以下對比以下他們的異同點。
1.CountDownLatch是讓一組線程等待在某個點,然後外界達到某種條件後則這組線程會被通過放行。Semaphore則是線程在許可池中嘗試獲取許可,獲取成功則直接通過,獲取不到則等待。所以Semaphore的目標不是讓線程協同,而是對運行的線程進行限量。
2.在實現上它們兩是非常相似的,它們都是通過自己實現AQS來達到目的,都是首先初始化state爲某個值。不同點在於各自對tryAcquireShared和tryReleaseShared的實現,對於CountDownLatch來說,只要state不爲0則代表外界條件還沒滿足,所以獲取共享資源統一返回失敗,從而實現了線程的等待,而對於Semaphore來說,是要切實的去判斷許可池是否還有可用的許可(即state減去本次獲取的許可後是否大於0)。而釋放資源的過程兩者的實現都是大同小異。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章