Semaphore is a kind of JDK built-in synchronizer in Java multithreading strategy. It can realize concurrent access control of common resources by multithreading. A thread must obtain a license before entering a public resource. If it cannot obtain the license, it must wait for other threads to release the license. Each thread will release the license when leaving the public resource. Semaphore can be thought of as a counter. If the value of the counter is less than the maximum permitted value, all threads calling acquire methods get a permit to proceed. By calling release, the counter is reduced by one.

The main application of semaphores is to control up to N threads accessing resources simultaneously, where the maximum value of the counter is the maximum allowable value N. Take a parking lot as an example. Suppose there are eight parking Spaces in the parking lot, six of which have been parked, and then two cars arrive, because there are exactly two parking Spaces left, both of them can be parked. Then another bus came, and now there was no room so we had to wait for another bus to leave. A red car left the parking lot, and the yellow car could just pull in. If another car came in, the car would have to wait. And so on. In this process, the parking lot is a common resource, the number of parking Spaces is the maximum allowed number of semaphore, and the vehicle is like a thread.

Four elements

The four elements of semaphore are: maximum license, fair mode, acquire method and release method. The maximum number of permissions and fair mode are specified when the Semaphore object is built, indicating, respectively, how many threads can simultaneously access a common resource and whether fair mode is used when obtaining a license. The acquire method is used to obtain permissions and enter a wait state if there are not enough permissions. The release method is used to release permissions.

The realization of unfair patterns

Semaphore class implementation is based on the AQS synchronizer implementation, regardless of fair mode or unfair mode is based on the AQS shared mode, but in the licensing operation logic is different. Semaphore’s default mode is unfair mode. Let’s look at the implementation of unfair mode first.

The main methods of the Semaphore class are shown below. It provides two constructors that take the maximum allowed and whether to use fair mode. FairSync is a fair mode synchronizer and NonfairSync is an unfair mode synchronizer. There are two acquire methods, and the default is to acquire one license at a time if no argument is passed, while the integer argument is passed to acquire several licenses at a time. Similarly, there are two release methods, with no parameter indicating that one license is released, and an integer parameter indicating that several licenses are released at once. Semaphore’s main methods are as follows

Semaphore’s Syn subclass is an abstract parent of the fair-mode FairSync class and the non-fair-mode NonfairSync class. The maximum number of permissions corresponds to the state variable of the AQS synchronizer. Because the schema is an unfair schema, the unfair permission acquireshared method nonfairTryAcquireShared is provided. The unfair mode is to allow all threads to spin as many licenses as possible, regardless of their first-come-first-come order, and all threads compete for licenses together. Wherein, the compareAndSetState method provides CAS algorithm to ensure concurrent modification of the license value, and the number of remaining licenses is equal to the current available license value minus the current consumed license value. It should be noted that when the number of remaining licenses is less than 0, it will return a negative number, resulting in threads entering the wait queue. The tryReleaseShared method provides the operation to release the license. Use this method in fair mode or not, and the logic for releasing the license is the same. Spin to increase the number of released licenses to the current number of remaining licenses.

Non-fair mode NonfairSync class implementation is mainly tryAcquiacquireshared method, directly call the parent class Sync nonfairTryAcquireShared method can be.

Realization of fair mode

The main difference between fair mode and unfair mode is the licensing mechanism. Unfair mode makes all threads compete for licenses directly by spinning, resulting in unfair mode. Fairness mode implements fairness mechanism through queue. The difference is in the tryAcquireShared method, let’s look at the tryAcquireShared method in fair mode. In fact, the difference is the two lines of code in the box below, which checks if there is already a wait queue, returns -1 if there is already a wait queue, returns -1 to tell the AQS synchronizer to put the current thread into the wait queue, queue means fair. In fact, this is not strictly fair, in the previous AQS synchronization fairness chapter has an in-depth discussion of AQS fairness, if you forget to refer to deepen understanding. Moreover, in order to reach the maximum number of permits, all threads do not enter the wait queue, but all threads spin permission.

Case 1

Let’s start with a simple example. We instantiate a semaphore object with five permissions, and then a total of 10 threads try to acquire five permissions. The thread that gets the permissions accumulates the value by one, sleeps for five seconds, and finally releases the permissions.

The following output is displayed: Five threads output “counting Number: xx” and the rest threads start to wait. Wait approximately 5 seconds for the five threads granted permission to perform the release operation before the other threads are granted permission to proceed.

Case 2

Example 2 is similar to example 1, except that it consumes 2 licenses each time a license is acquired and 2 licenses are released on the same release. Here we instantiate a semaphore object with six permissions, and then 10 threads try to obtain the permissions together. However, only 3 threads can be allowed at the same time, that is, three threads can be allowed to accumulate value by 1, and then sleep for 5 seconds before releasing permission. The other three threads are then allowed to continue until all 10 threads have finished.

conclusion

This article introduced a built-in SYNCHRONizer in the JDK, Semaphore, that allows you to control up to a number of threads accessing a common resource. It can be thought of as a counter, and a thread can proceed if the value of the counter is less than the permitted maximum, whereas a thread can only wait. We analyze the implementation principle of Semaphore in depth. It is implemented based on AQS synchronizer and provides two modes of fair and unfair, and we analyze the implementation of the two modes respectively. Through this article we have been able to understand the Semaphore mechanism in great depth and clarity.