JUC框架 Semaphore源碼解析 JDK8

前言

Semaphore在多線程協作中常常用來控制公共資源的訪問,限制同時訪問數量。

打個比方,Semaphore就像是一個裝有令牌(permit)的黑箱子,拿到令牌的人才能去做愛做的事情,誰都可以從裏面拿走若干令牌,誰都可以把新的令牌扔到裏面去,但Semaphore從來不記載誰拿走的令牌。

重要成員

CountDownLatch一樣,Semaphore依賴的也是AQS的共享鎖,核心屬性也是AQS的state成員。在Semaphore中,使用內部類繼承AQS使用。

構造器

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

默認構造器使用非公平鎖,可以通過參數fair來得到公平鎖。參數permits最終會賦值給AQS的state成員。

獲取信號量(減小state)

Semaphore方法 調用的AQS方法 是否阻塞 是否響應中斷 是否超時機制 返回值及含義
acquire() sync.acquireSharedInterruptibly(1) - void
acquire(int permits) sync.acquireSharedInterruptibly(permits) - void
acquireUninterruptibly() sync.acquireShared(1) - - void
acquireUninterruptibly(int permits) sync.acquireShared(permits) - - void
tryAcquire(long timeout, TimeUnit unit) sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)) boolean
返回時是否獲得了鎖
tryAcquire(int permits, long timeout, TimeUnit unit) sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout)) boolean
返回時是否獲得了鎖
tryAcquire() sync.nonfairTryAcquireShared(1) >= 0 - boolean
返回時是否獲得了鎖
  • 上面表格中,所有沒有參數的Semaphore方法,實質上都是在獲得單位1的信號量。
  • 調用的AQS方法,其實只有三種,acquireShared共享鎖的獲取與釋放中已經進行了講解),acquireSharedInterruptiblytryAcquireSharedNanosCountDownLatch源碼解析中已經進行了講解)。這三者的區別已經在表格列出。
    • 需要響應中斷,方法聲明會拋出中斷異常。
    • 有超時機制,就需要用返回值區別 獲得鎖返回 和 超時都沒獲得到鎖 兩種情況。
    • 這三個方法都需要調用到AQS子類實現的tryAcquireShared,該方法用來獲取共享鎖,子類可以將其實現公平鎖或是非公平鎖。
  • nonfairTryAcquireShared其實不大應該放在上表裏面,因爲它根本不是AQS的方法,只是AQS子類的新加方法。因爲它根本沒有阻塞等待的過程,只是簡單的try一次,成功失敗聽天由命,所以它根本不會阻塞。

表格中,帶permits參數的方法可以獲得單位不爲1的信號量,但是方法中對permits參數的檢查要求是>=0,也就是說,允許線程獲得0單位的信號量,雖然我感覺這樣沒有任何意義。

這裏我們再複習一下tryAcquireShared返回值的含義:

  • 如果返回值大於0,說明獲取共享鎖成功,並且後續獲取也可能獲取成功。
  • 如果返回值等於0,說明獲取共享鎖成功,但後續獲取可能不會成功。
  • 如果返回值小於0,說明獲取共享鎖失敗。

非公平實現的tryAcquireShared

//NonfairSync
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

//Sync
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || 
            compareAndSetState(available, remaining))
            return remaining;
    }
}
  • 方法使用了自旋,這是合理且必要的。共享鎖是共享的,自然可能有多個線程正在同時執行上面的代碼,即使失敗了也不能退出循環,而是應該失敗後再得到當前值,然後再次CAS嘗試。
  • 如果remaining算出來小於0,說明剩餘信號量已經不夠拿的了,那就直接返回remaining這個負數(表達獲取共享鎖失敗),不做CAS操作。
  • 如果remaining算出來大於等於0,說明剩餘信號量夠拿的,緊接着如果CAS設置成功,就返回remaining這個大於等於0的數(表達獲取共享鎖成功)。
  • 這個方法想要退出,只有當前線程拿到了想要數量的信號量,或剩餘信號量已經不夠拿。

公平實現的tryAcquireShared

//FairSync
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;
    }
}

區別就是要先判斷同步隊列中是否已經有節點了(hasQueuedPredecessors),如果有那同步隊列中的節點屬於是排在當前線程之前的,所以只好直接返回-1。

釋放信號量(增加state)

釋放信號量就不用區別什麼公平不公平了。

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;
    }
}
  • 考慮多線程,使用自旋保證releases單位的信號量能夠釋放到位。
  • 只有CAS設置成功,或溢出int型的範圍,才能退出這個循環。

工具方法

tryAcquire

上面表格裏,一直沒有好好講tryAcquire,因爲它在裏面屬於一個異類,沒有阻塞等待的過程。

    public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

nonfairTryAcquireShared的實現也看過了,裏面根本沒有調用過LockSupport.park,確實沒有阻塞。不要被自旋所迷惑,自旋也不是阻塞,而且這個自旋過程一般情形下很快就會結束。

簡而言之,Semaphore#tryAcquire的作用就是嘗試 一次性的、非公平的 獲得鎖動作。注意這種一次性動作一定要是非公平實現的,不然大部分情況下(同步隊列中只要有一個線程在等待),這種一次性動作肯定不能成功。這也是爲什麼要把非公平實現放到NonfairSyncFairSync的父類裏的一個公共方法裏。

注意,返回值進行了處理,如果獲得共享鎖成功(nonfairTryAcquireShared返回值>=0),返回true;如果獲得共享鎖失敗(nonfairTryAcquireShared返回值<0),返回false。

reducePermits

//Semaphore
protected void reducePermits(int reduction) {
    if (reduction < 0) throw new IllegalArgumentException();
    sync.reducePermits(reduction);
}

//Sync
final void reducePermits(int reductions) {
    for (;;) {
        int current = getState();
        int next = current - reductions;
        if (next > current) // underflow
            throw new Error("Permit count underflow");
        if (compareAndSetState(current, next))
            return;
    }
}
  • 這個方法的作用也是獲得信號量,只不過這個函數相比nonfairTryAcquireShared的實現,它允許改變後的信號量是負數。
  • 自旋的過程。想要退出函數,只有CAS操作成功或者向下溢出了。
  • if (next > current)分支進入的原因只能是int型變量向下溢出,因爲reductions被保證是一個>=0的數。
  • protected方法,用起來不是那麼方便。
//Sync
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || 
            compareAndSetState(available, remaining))
            return remaining;
    }
}

放一下nonfairTryAcquireShared的實現作對比。

drainPermits

//Semaphore
public int drainPermits() {
    return sync.drainPermits();
}

//Sync
final int drainPermits() {
    for (;;) {
        int current = getState();
        if (current == 0 || compareAndSetState(current, 0))
            return current;
    }
}

自旋的過程,直到信號量被清空爲0。

總結

  • Semaphore的AQS子類是一個很標準的共享鎖實現。
    • 獲得信號量 == 減小AQS的state。
    • 釋放信號量 == 增加AQS的state。
  • 共享鎖的AQS子類實現方法需要自旋,這一點在Semaphore和CountDownLatch都有體現。獨佔鎖的AQS子類實現方法不需要自旋。
  • 獲得信號量失敗一定是因爲信號量已經減到0了,且獲得失敗就會阻塞。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章