Semaphore

Semaphore信號量,可以控同時訪問的線程個數(shù),acquire() 失敗就等待,而 release() 釋放一個許可。
來看看例子:

public class SemaphoreTrain {

    static class Worker extends Thread {
        private int n;
        private Semaphore semaphore;

        public Worker(int n, Semaphore semaphore) {
            this.n = n;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("Worker num " + n + " use machine");
                Thread.sleep(2000);
                System.out.println("Worker num " + n + " stop use");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int worker = 6;    //工人數(shù)
        int machine = 4;  //機器數(shù)
        Semaphore semaphore = new Semaphore(machine);
        for (int i = 0; i < worker; i++) {
            new Worker(i, semaphore).start();
        }
    }
}
Worker num 1 use machine
Worker num 3 use machine
Worker num 2 use machine
Worker num 0 use machine
Worker num 0 stop use
Worker num 1 stop use
Worker num 3 stop use
Worker num 2 stop use
Worker num 4 use machine
Worker num 5 use machine
Worker num 4 stop use
Worker num 5 stop use

只允許最多四個線程同時運行,其它的等待執(zhí)行線程release退出。
來看看源碼實現(xiàn):

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

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

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

這里說點我對線程安全的思考:同步狀態(tài)在AQS中是valatile的,確保了可見性,Doug Lea大神在設(shè)計JUC框架時采用了非鎖式的方法來確保線程安全,不排他也就是正在運行任務(wù)的線程也可能因為調(diào)度的原因被新的線程獲取到執(zhí)行資格,但是新的線程想要執(zhí)行任務(wù)是無法獲取到資格的,這個資格由同步狀態(tài)來實現(xiàn),它會被加到等待隊列中去。
以nonfairTryAcquireShared方法為例:A線程getState獲取到最新state個數(shù),B 線程搶到執(zhí)行資格更改了state,回到A當它想要計算remaining值時,A所緩存的state值會失效它會重新從內(nèi)存中獲取;這時又一線程搶過來更改了state,再回到A當它利用CAS更改state值時會執(zhí)行失敗,重新循環(huán)再來一遍,之前說過循環(huán)CAS就是Java的一種鎖實現(xiàn)方式,另一種是內(nèi)置鎖Synchronized。
非公平鎖

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

之前ReentrentLock里分析過,公平與非公平的不同就在對tryAcquire處理上,由于插隊可能發(fā)生在兩個地方,一個是頭節(jié)點release歸零了同步狀態(tài)還未喚醒后繼節(jié)點;另一處是后繼節(jié)點被喚醒,在acquireQueued的for循環(huán)判斷里;這兩處都會嘗試tryAcquire,存在被剛acquire的線程插隊的情況。
而解決辦法就是判斷等待隊列中是否已有正在等待的線程,對于公平鎖來說有那么你就直接被pass掉,輪不到你。
構(gòu)造函數(shù)

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

//-----------------------------方法在AQS中
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
//小于零說明沒有獲得許可,需要等待,對于公平鎖來說沒輪到你就直接返回-1,邏輯在hasQueuedPredecessors里
        if (tryAcquireShared(arg) < 0)  
            doAcquireSharedInterruptibly(arg);
    }
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
//這里shouldParkAfterFailedAcquire會將前面節(jié)點的狀態(tài)改為Signal
//對于等待隊列中節(jié)點的waitStatus初始為0,之后由后加入的節(jié)點改為SIGNAL
//SIGNAL這個狀態(tài)它表明你有后繼節(jié)點,release時喚醒它
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

release

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

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

來說說doReleaseShared,前面在說shouldParkAfterFailedAcquire時說過SIGNAL節(jié)點表明你有后繼節(jié)點,那么當你的waitStatus == 0時代表等待隊列中沒有等待的線程。那么這種情況下就將它改為PROPAGATE狀態(tài),之后在進入等待隊列中的節(jié)點會將它改為SIGNAL,這也表明HEAD不一定代表正在運行的線程,我想HEAD就是一個頭的標識,每個節(jié)點的狀態(tài)代表了release時要進行的處理。

總結(jié)

Semaphore維持了一個同步狀態(tài)state(大小初始化時設(shè)定,代表最大允許執(zhí)行線程數(shù)),在acquire時將state減1,小于零加入同步隊列等待,大于零則CAS更改值。
由于Semaphore與之前的同步器在實現(xiàn)原理上沒什么不同,所以本具體每一步驟就不介紹了,只說了說我在看它源碼時的一些思考。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。