Semaphore manages a range of licenses; Acquire a license through acquire method. If acquire a license, return it directly, otherwise enter blocking state. Release the license, which wakes up a thread if it is blocked by calling Acquire. Typically used to manage a common resource and allow only a specified number of threads to access it at a time. In Java. Util. Concurrent concurrent classes are basically using AQS (AbstractQueuedSynchronizer).

1 constructor

Here is the Semaphore constructor:

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

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

The difference between the two constructors is whether to specify lock fairness. A fair lock means that the lock is obtained in the order in which the lock is applied. An unfair lock means that when the resource is released, multiple threads applying for the resource compete for the lock. The one who grabs the lock belongs to the one.

2 acquire

Request a license. If there are enough licenses in the current Semaphore, this method will return immediately. If there are not enough licenses in the current Semaphore, the thread calling this method will block until another thread releases the license via release. Here is the acquire code for fair locks:

public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } 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; } } 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; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); }}Copy the code

3 release

The release method adds a license and wakes up a thread blocking through acquire.

public void release() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } 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; } } 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; }}Copy the code

4 sample

The following example creates three threads that all attempt to obtain a license for a Semaphore object. Because there is only one license, only one thread can execute a doTask method at a time. When a thread finishes executing, it wakes up a waiting thread to continue executing.

public static void main(String[] args) { Semaphore semaphore = new Semaphore(1); for (int i = 0; i < 3; i++) { String name = "p-"+i; Thread Thread = new Thread(() -> {try {system.out.println (" time ="+ dateutil.getCurrentTime ()+" +name+"); semaphore.acquire(); doTask(name); semaphore.release(); System.out.println(" time ="+ dateutil.getCurrentTime ()+" user "+name+" resource release completed "); } catch (InterruptedException e) { e.printStackTrace(); }}); thread.start(); }} private static void doTask(String name){system.out.println (" dateutil.getCurrentTime ()+ dateutil.getCurrentTime ()+" "); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }}Copy the code

Here is the output, which you can see is consistent with the analysis at the beginning of the example.

Time =10:40:07:703 User P-2 starts to obtain resources Time =10:40:07:741 User P-2 starts to use resources Task execution time =10:40:07:703 User P-1 starts to obtain resources =10:40:07:703 User P-0 starts to obtain resources =10:40:10:745 User P-1 starts to use resources. Task Execution time =10:40:10:745 User P-2 Resource release completion time =10:40:13:748 User P-1 Resource release completion time =10:40:13:748 User P-0 starts to use resources. Time =10:40:16:751 User P-0 Resources are releasedCopy the code