Java多執行緒同步工具類之Semaphore

bigfan發表於2019-07-06

Semaphore訊號量通常做為控制執行緒併發個數的工具來使用,它可以用來限制同時併發訪問資源的執行緒個數。

一、Semaphore使用

下面我們通過一個簡單的例子來看下Semaphore的具體使用,我們同時執行10個計數執行緒,並定義一個Semaphore變數用來控制併發值,同一時間只允許兩個執行緒併發執行;

    public static void main(String[] args) {

        Semaphore semaphore = new Semaphore(2);

        // 啟動計數執行緒
        for (int i = 1; i <= 10; i++) {
            new SemaphoreThread(semaphore).start();
        }
    }

計數執行緒

public class SemaphoreThread extends Thread {

    private Semaphore semaphore;

    public SemaphoreThread(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    public void run() {
        try {
            semaphore.acquire();//獲取執行許可
            Thread.sleep(2000);
            System.out.println(this.getName() + "執行緒," + "開始進行計數");
            // 模擬計數時長
            Thread.sleep(2000);
            // 一個執行緒完成,允許下一個執行緒開始計數
            System.out.println(this.getName() + "執行緒," + "計數完畢");
            semaphore.release();//歸還許可

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

輸出結果

Thread-0執行緒,開始進行計數
Thread-1執行緒,開始進行計數
Thread-1執行緒,計數完畢
Thread-0執行緒,計數完畢
Thread-2執行緒,開始進行計數
Thread-3執行緒,開始進行計數
Thread-2執行緒,計數完畢
Thread-3執行緒,計數完畢
Thread-4執行緒,開始進行計數
Thread-5執行緒,開始進行計數
Thread-5執行緒,計數完畢
Thread-4執行緒,計數完畢
Thread-6執行緒,開始進行計數
Thread-7執行緒,開始進行計數
Thread-6執行緒,計數完畢
Thread-7執行緒,計數完畢
Thread-8執行緒,開始進行計數
Thread-9執行緒,開始進行計數
Thread-8執行緒,計數完畢
Thread-9執行緒,計數完畢

通過輸出結果可以看出,Semaphore根據我們設定的併發值限制了執行緒同時執行的個數,每次只執行兩個執行緒進行計數。

二、Semaphore原始碼分析

接下來我們對Semaphore具體的內部實現進行分析與總結

1、Semaphore的構造

public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    
    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);
        }
    }
    
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;
        /**
        1、設定AbstractQueuedSynchronizer中同步狀態的值state,也就是計數器的值。
        2、這個值volatile變數,必須保證執行緒間的可見性;
        **/
        Sync(int permits) {
            setState(permits);
        }

        //獲取state的值
        final int getPermits() {
            return getState();
        }

        //通過CAS方式減少state值,對應Semaphore的acquire獲取許可
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } //通過CAS方式增加state值,對應Semaphore的release歸還許可 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; } } //減少許可 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; } } //許可置0 final int drainPermits() { for (;;) { int current = getState(); if (current == 0 || compareAndSetState(current, 0)) return current; } } }

通過程式碼可以看出Semaphore也是基於AbstractQueuedSynchronizer類來實現的,它會根據你傳入的併發執行緒數量來構造一個繼承自AbstractQueuedSynchronizer的Syc實現類;

2、acquire方法

Semaphore的acquire方法實現獲取執行許可,acquire方法底層呼叫的其實是AbstractQueuedSynchronizer的acquireSharedInterruptibly方法,我們看下具體程式碼

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //tryAcquireShared由Semaphore的Sync類的nonfairTryAcquireShared方法具體實現
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

從上面我們已經知道nonfairTryAcquireShared方法內部其實是一個針對state值減法操作,並通過CAS操作改變同步狀態State的值,直到要獲取的許可執行緒超過設定的併發值,tryAcquireShared(arg)返回值小於0,執行doAcquireSharedInterruptibly方法開始嘗試獲取鎖,並進入阻塞;

3、release方法

Semaphore的release方法對應釋放執行許可

    public void release() {
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) { //tryAcquireShared由Semaphore的Sync類的tryReleaseShared方法具體實現,執行歸還許可操作; if (tryReleaseShared(arg)) { //釋放鎖狀態,喚醒阻塞執行緒 doReleaseShared(); return true; } return false; }

執行tryReleaseShared方法歸還歸許可,對state值做加法操作,沒有問題的話返回true值,執行doReleaseShared方法釋放鎖,喚醒阻塞執行緒。

三、總結

執行緒併發個數控制工具Semaphore類與CountDownLatch類似,都是基於AbstractQueuedSynchronizer類實現的,通過操作同步狀態state值結合共享鎖的模式控制一個或多個執行緒的執行從而實現具體的功能。以上就是對Semaphore類使用與原始碼進行的分析與總結,其中如有不足與不正確的地方還望指出與海涵。

 

關注微信公眾號,檢視更多技術文章。

相關文章