Java併發Concurrent包的鎖(七)——Semaphore源碼分析及使用

Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。

Sync類

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;
        // 許可總數量,使用AQS的state來保存
        Sync(int permits) {
            setState(permits);
        }
        // 獲取當前剩餘許可數量
        final int getPermits() {
            return getState();
        }
        // 非公平的獲取指定數量的許可,返回剩餘的數量
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                // 這裏剩餘的如果小於0,會造成短路,並沒有CAS更新的操作
                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;
            }
        }
        // 削減指定個數的許可數量
        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;
            }
        }
        // 獲取並返回立即可用的所有許可
        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

NonfairSync

非公平版本的 Sync 。

    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);
        }
    }

FairSync

非公平的 Sync 當然要判斷隊列頭節點正在等待的是不是當前線程,如果不是的話當前線程要排隊等待。

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

構造函數

    // 默認的構造函數是非公平的
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    // 給定訪問許可總數來構造公平或非公平的信號量
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

重要方法

acquire()

從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷。

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

acquireUninterruptibly()

從此信號量中獲取許可,在有可用的許可前將其阻塞。

    public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }

tryAcquire()

僅在調用時此信號量存在一個可用許可,才從信號量獲取許可。
nonfairTryAcquireShared 方法如上邊展示的,剩餘爲負數時,並不更新 state 的值。說明此次獲取是失敗的。

    public boolean tryAcquire() {
        // 調用Sync中的方法,如果獲取1個資源後剩餘的大於等於0,返回true,如果剩餘的爲負數,返回false。
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

release()

釋放一個許可,將其返回給信號量。

    public void release() {
        sync.releaseShared(1);
    }

acquire(int permits)

從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。

    public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }

acquireUninterruptibly(int permits)

從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞。

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

tryAcquire(int permits)

僅在調用時此信號量中有給定數目的許可時,才從此信號量中獲取這些許可。

    public boolean tryAcquire(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.nonfairTryAcquireShared(permits) >= 0;
    }

release(int permits)

釋放給定數目的許可,將其返回到信號量。

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

availablePermits()

返回此信號量中當前可用的許可數。

    public int availablePermits() {
        return sync.getPermits();
    }

drainPermits()

獲取並返回立即可用的所有許可。

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

reducePermits(int reduction)

根據指定的縮減量減小可用許可的數目。

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

使用情景

我們以銀行辦業務的例子來說明,假設銀行有 4 個櫃檯,一次進來了 10 個辦理業務的人:

public class TestSemaphore {

    private static Semaphore semaphore;

    // 銀行櫃檯
    public static class BankCounter {

        // 辦業務
        public void banking() throws InterruptedException{
            semaphore.acquire();
            System.out.println("用戶" + Thread.currentThread().getName() + "開始處理!");
            // 處理業務的時間
            int waitTime = (int)(Math.random()*10 +1);
            Thread.sleep(waitTime*1000);
            System.out.println("用戶" + Thread.currentThread().getName() + "處理結束,共用時 "+ waitTime +" 秒,離開窗口,下一個!");
            semaphore.release();
        }

    }

    // 等待窗口辦業務的線程
    public static class WindowThread extends Thread{

        @Override
        public void run() {
            try {
                new BankCounter().banking();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {

        // 信號量設置爲4,等於只有4個窗口資源,設置爲公平模式排隊
        semaphore = new Semaphore(4, true);

        // 啓動10個線程,10個用戶在等待辦業務,500ms延遲是爲了線程編號從小到大公平的排隊
        for(int i=0; i<10; i++){
            new WindowThread().start();
            Thread.sleep(500);
        }

    }

}

運行結果:

用戶Thread-0開始處理!
用戶Thread-1開始處理!
用戶Thread-2開始處理!
用戶Thread-3開始處理!
用戶Thread-1處理結束,共用時 2 秒,離開窗口,下一個!
用戶Thread-4開始處理!
用戶Thread-0處理結束,共用時 4 秒,離開窗口,下一個!
用戶Thread-5開始處理!
用戶Thread-2處理結束,共用時 4 秒,離開窗口,下一個!
用戶Thread-6開始處理!
用戶Thread-3處理結束,共用時 7 秒,離開窗口,下一個!
用戶Thread-7開始處理!
用戶Thread-4處理結束,共用時 7 秒,離開窗口,下一個!
用戶Thread-8開始處理!
用戶Thread-6處理結束,共用時 6 秒,離開窗口,下一個!
用戶Thread-9開始處理!
用戶Thread-9處理結束,共用時 1 秒,離開窗口,下一個!
用戶Thread-5處理結束,共用時 9 秒,離開窗口,下一個!
用戶Thread-7處理結束,共用時 8 秒,離開窗口,下一個!
用戶Thread-8處理結束,共用時 7 秒,離開窗口,下一個!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章