Article catalog
summary
Semaphore semaphore is also a synchronizer in Java. Different from CountDownLatch and cyclebarier, its internal counter is incremented, and an initial value can be specified when initializing semaphore at the beginning. However, it is not necessary to know the number of threads to be synchronized, but to specify the number of threads to be synchronized when calling acquire method where synchronization is required.
Small Demo
Similarly, the following example is to open two sub threads in the main thread for execution. After all sub threads are executed, the main thread will continue to run downward.
import java.time.LocalTime; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * @author Small craftsman * @version 1.0 * @description: TODO * @date 2021/12/14 23:59 * @mark: show me the code , change the world */ public class SemphoreTest { // 1 create a semaphore instance. The current semaphore counter value is 0 private static Semaphore semaphore = new Semaphore(0); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2); // Thread 1 commits to the thread pool executorService.submit(() -> { System.out.println(Thread.currentThread().getName() + " end of execution " + LocalTime.now()); // Call the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1 semaphore.release(); }); // Thread 2 commits to the thread pool executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " end of execution " + LocalTime.now()); // Call the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1 semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }); // 1 wait for the child thread to complete the task and return semaphore.acquire(2); System.out.println(Thread.currentThread().getName() + "End of task execution " + LocalTime.now()) ; // Close thread pool executorService.shutdown(); } }

- First, a semaphore instance is created. The input parameter of the constructor is 0, indicating that the value of the current semaphore counter is 0
- Then the main function adds two thread tasks to the thread pool and calls the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1
- Finally, call the acquire method of the semaphore in the main thread. The parameter is 2, indicating that the thread calling the acquire method will be blocked until the semaphore count becomes 2
You can see here that if the parameter passed in Semaphore is N, and the release method is called in the M thread, then the parameter passed by acquire when M threads are synchronized should be M+N.
The following is an example to simulate the function of [CyclicBarrier multiplexing], and the code is as follows
import java.time.LocalTime; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * @author Small craftsman * @version 1.0 * @description: TODO * @date 2021/12/14 23:59 * @mark: show me the code , change the world */ public class SemphoreTest2 { // 1 create a semaphore instance private static Semaphore semaphore = new Semaphore(0); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2); // Thread 1 commits to the thread pool executorService.submit(() -> { System.out.println(Thread.currentThread().getName() + " end of execution " + LocalTime.now()); // Call the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1 semaphore.release(); }); // Thread 2 commits to the thread pool executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " end of execution " + LocalTime.now()); // Call the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1 semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }); // 1 wait for the child thread to complete the task and return semaphore.acquire(2); // Thread 3 commits to the thread pool executorService.submit(() -> { System.out.println(Thread.currentThread().getName() + " end of execution " + LocalTime.now()); // Call the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1 semaphore.release(); }); // Thread 4 commits to the thread pool executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " end of execution " + LocalTime.now()); // Call the release method of the semaphore inside each thread, which is equivalent to incrementing the counter value by 1 semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }); // 2. Wait for the child thread to complete the task and return semaphore.acquire(2); System.out.println(Thread.currentThread().getName() + "End of task execution " + LocalTime.now()) ; // Close thread pool executorService.shutdown(); } }
- First, add thread 1 and thread 2 to the thread pool. The main thread is blocked after executing code (1). After thread 1 and thread 2 call the release method, the semaphore value changes to 2. At this time, the acquire method of the main thread will return after obtaining 2 semaphores (the current semaphore value is 0 after return).
- Then, the main thread adds thread 3 and thread 4 to the thread pool. After the main thread executes code (2), it is blocked (because the main thread needs to obtain 2 semaphores, and the current number of semaphores is 0). The main thread returns only after thread 3 and thread 4 execute the release method.
It can be seen from this example that Semaphore realizes the reuse function of CyclicBarrier to some extent.
Class relationship overview

It can be seen from the class diagram that Semaphore is still implemented using AQS. Sync is only a modification of AQS, and Sync has two implementation classes, which are used to specify whether fair policies are adopted when obtaining semaphores.
For example, the following code will use a variable to specify whether to use the fairness policy when creating Semaphore.
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); } Sync(int permits) { setState(permits); }
Semaphore adopts the unfair policy by default. If you need to use the fair policy, you can use the constructor with two parameters to construct the semaphore object.
In addition, just as the number of initialization semaphores permits passed by the CountDownLatch constructor is assigned to the AQS state variable, the AQS state value here also represents the number of semaphores currently held.
Core method source code interpretation
void acquire()
public void acquire() throws InterruptedException { // The pass parameter is 1, indicating that a semaphore resource is to be obtained sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { // 1 . If the thread is interrupted, an interrupt exception is thrown if (Thread.interrupted()) throw new InterruptedException(); // 2 otherwise, call the Syn subclass method to try to get it again if (tryAcquireShared(arg) < 0) // If the acquisition fails, it will be put into the blocking queue and try again. If it fails, it will call the park method to suspend the current thread doAcquireSharedInterruptibly(arg); }
acquire() internally calls the acquiresharedinterruptible method of Sync, which will respond to the interrupt (throw an interrupt exception if the current thread is interrupted).
Tryacquishared, an AQS method that attempts to obtain semaphore resources, is implemented by a subclass of Sync, so it is discussed here from two aspects.
tryAcquireShared method of non fair policy NonfairSync class

Continue to look at nonfairtryacquisureshared

final int nonfairTryAcquireShared(int acquires) { for (;;) { // Gets the value of the current semaphore int available = getState(); // Calculate current residual value int remaining = available - acquires; // If the current value is < 0 or CAS setting is successful, return if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
- First obtain the current semaphore value (available), and then subtract the values to be obtained (acquire) to obtain the remaining semaphores
- If the remaining value is less than 0, it means that the current number of semaphores cannot meet the demand. Then a negative number is returned directly. At this time, the current thread will be put into the blocking queue of AQS and suspended.
- If the residual value is greater than 0, use CAS operation to set the current semaphore value as the residual value, and then return the residual value.
In addition, NonFairSync is obtained unfairly, that is, the thread that calls the acquire method to obtain the semaphore first does not necessarily obtain the semaphore first than the latecomer.
for instance:
- Thread A first calls the acquire () method to obtain semaphores, but the current number of semaphores is 0, then thread A will be put into the AQS blocking queue
- After A period of time, thread C calls the release () method to release A semaphore. If no other thread gets the semaphore, thread A will be activated and get the semaphore
- However, if thread C calls the acquire method after releasing the semaphore, thread C will compete with thread A for the semaphore resource.
If the unfair strategy is adopted, it can be seen from the code of nonfairtryacquisureshared that thread C can obtain the semaphore before thread A is activated or before thread A is activated, that is, in this mode, the blocking thread and the currently requested thread are competitive, rather than following the first come, first served strategy.
tryAcquireShared method of fair policy FairSync class

/** * Fair version */ 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; } } }
It can be seen that fairness is guaranteed by the function hasQueuedPredecessors. The previous blog posts focused on hasqueued predecessors. The fairness strategy is to see whether the predecessor node of the current thread node is also waiting to obtain the resource. If so, it will give up the obtained permission, and then the current thread will be put into the AQS blocking queue, otherwise it will be obtained.
void acquire(int permits)
This method is different from the acquire() method. The latter only needs to obtain one semaphore value, while the former obtains permits.
public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits); }
void acquireUninterruptibly()
This method is similar to acquire(). The difference is that it does not respond to interrupts. That is, when the previous thread calls acquireunteruptibly to obtain resources (including after being blocked), other threads call the interrupt () method of the current thread and set the interrupt flag of the current thread. At this time, the current thread will not throw an InterruptedException exception and return.
public void acquireUninterruptibly() { sync.acquireShared(1); }
Look at the response to the interrupt

void acquireUninterruptibly(int permits)
This method differs from the acquire (int permissions) method in that it does not respond to interrupts.
public void acquireUninterruptibly(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.acquireShared(permits); }
void release()
The function of this method is to increase the Semaphore value of the current Semaphore object by 1. If a thread is put into the AQS blocking queue because calling the acquire method is blocked, it will select a thread whose Semaphore number can be satisfied according to the fair policy for activation, and the activated thread will try to obtain the newly increased Semaphore.
public void release() { // One semaphore is released by default sync.releaseShared(1); }
/** * Releases in shared mode. Implemented by unblocking one or more * threads if {@link #tryReleaseShared} returns true. * * @param arg the release argument. This value is conveyed to * {@link #tryReleaseShared} but is otherwise uninterpreted * and can represent anything you like. * @return the value returned from {@link #tryReleaseShared} */ public final boolean releaseShared(int arg) { // 2 attempt to free resources if (tryReleaseShared(arg)) { // 3. If the resource is released successfully, call the park method to wake up the AQS The first suspended thread in the queue 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)) // cas return true; }
By the code release() - > sync Releaseshared (1) shows that the release method only increases the semaphore value by 1 each time. The tryrereleaseshared method is an infinite loop. CAS ensures the atomic operation of the release method to increase the semaphore by 1. After the tryrereleaseshared method successfully increases the semaphore value, the code (3) dorreleaseshared();, That is, the AQS method is called to activate the thread blocked by calling the aquire method.
private void doReleaseShared() { /* * Ensure that a release propagates, even if there are other * in-progress acquires/releases. This proceeds in the usual * way of trying to unparkSuccessor of head if it needs * signal. But if it does not, status is set to PROPAGATE to * ensure that upon release, propagation continues. * Additionally, we must loop in case a new node is added * while we are doing this. Also, unlike other uses of * unparkSuccessor, we need to know if CAS to reset status * fails, if so rechecking. */ 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; } }
void release(int permits)
The difference between this method and the release method without parameters is that each call of the former will increase permissions based on the original semaphore value, while the latter will increase 1 each time.
public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); }
In addition, you can see that sync Releaseshared is a shared method, which indicates that the semaphore is shared by threads. The semaphore is not bound to fixed threads. Multiple threads can use CAS to update the semaphore value at the same time without being blocked.
Summary
Semaphore is also implemented using AQS, and there are fair strategies and unfair strategies when obtaining semaphores.