This is the second article in the concurrent thread tool class. In the first article, we analyzed CountDownLatch for your reference

One article to understand CountDownLatch usage and source code!

So in this article we will continue to talk to you about the second article in the concurrency tool class, Semaphore.

Know the Semaphore

What is the Semaphore

Semaphore, commonly translated as Semaphore, is also a thread synchronization tool. It is mainly used by multiple threads to operate on a shared resource in parallel. It represents a concept of permission, permission to allow multiple threads to operate on the same resource. Using Semaphore, you can control the number of concurrent threads accessing the resource.

Usage scenarios for Semaphore

Semaphore is mainly used for traffic control, such as database connections. There is a limit on the number of database connections that can be used at the same time. When the number of database connections reaches a certain limit, subsequent threads must wait for the preceding threads to release the database connections before they can acquire the database connections.

Another example is the traffic light on the highway. When the green light is on, only 100 cars can pass, while when the red light is on, vehicles are not allowed to pass.

Another example is the scene of parking lot. A parking lot has a limited number of parking Spaces and how many cars can be accommodated at the same time. When the parking space is full, the cars inside the parking lot can only be entered after leaving the cars outside the parking lot.

Semaphore use

Let’s simulate the business scenario of the parking lot: before entering the parking lot, there will be a warning board, which shows how many parking Spaces are left. When the parking space is 0, you can’t enter the parking lot. When the parking space is not 0, vehicles will be allowed to enter the parking lot. So parking lots have several key factors: the total capacity of parking lots, when a car enters, the total capacity of parking lots is -1, when a car leaves, the total capacity of parking lots is + 1, when there is not enough parking lots, cars can only wait outside the parking lot.

public class CarParking {

    private static Semaphore semaphore = new Semaphore(10);

    public static void main(String[] args){

        for(int i = 0; i<100; i++){ Thread thread =new Thread(new Runnable() {
                @Override
                public void run(a) {
                    System.out.println("Welcome" + Thread.currentThread().getName() + "Come to the parking lot.");
                    // Check whether parking is allowed
                    if(semaphore.availablePermits() == 0) {
                        System.out.println("Not enough parking space. Please be patient.");
                    }
                    try {
                        // Try to get
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "Enter the parking lot");
                        Thread.sleep(new Random().nextInt(10000));// Simulate the time of vehicles in the parking lot
                        System.out.println(Thread.currentThread().getName() + "Drive out of the parking lot.");
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, i + "Car no."); thread.start(); }}}Copy the code

In the code above, we gave the initial capacity of Semaphore, which was only 10 parking Spaces, and we used those 10 Spaces to control the flow of 100 cars, so the result was very similar to what we expected, with most cars in the waiting state. At the same time, however, some cars are still allowed to enter the parking lot. If a car enters the parking lot, Semaphore. Acquire will occupy a parking space, and when it leaves the parking lot, Semaphore. Release will give up a parking space, allowing the car behind to enter again.

Model of Semaphore Semaphore

The above code is relatively simple, but it gives us an insight into the guts of a semaphore model. Here is a semaphore model:

To explain Semaphore, Semaphore has an initial capacity. This initial capacity is the Semaphore that Semaphore can allow. After the acquire method is called, the capacity of Semaphore is -1, and after the release method is called, the capacity of Semaphore is + 1. During this process, the counter is always monitoring the number of Semaphore changes. When the traffic exceeds the capacity of Semaphore, the excess traffic will be queued in the waiting queue. Wait until Semaphore capacity permits before re-entering.

The traffic Semaphore controls is essentially threads, because threads are the primary focus of concurrency tools.

Here’s how it works

This picture should be easy to understand, but I won’t explain it too much here.

Semaphore understands

After understanding the basic use of Semaphore and Semaphore model, we still have to talk to you about the details of Semaphore from the source code, because the core of my article is to let my readers know about XXX, read this one is enough, this is my pursuit of writing articles, Good words do not say, the source code to go!

Semaphore Basic properties

There is only one attribute in Semaphore

private final Sync sync;
Copy the code

Sync is a synchronization implementation of Semaphore. Semaphore ensures thread-safety in a manner similar to ReentrantLock and CountDownLatch, both inherited from AQS implementations. Equally, the Sync will be inherited from AbstractQueuedSynchronizer a variable, that is to say, the Semaphore is not open around chatting AQS, so AQS really is too important.

Semaphore’s fairness and unfairness

So let’s go inside Sync and see what methods it implements

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

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

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

  final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
      int available = getState();
      int remaining = available - acquires;
      if (remaining < 0 ||
          compareAndSetState(available, remaining))
        returnremaining; }}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(a) {
    for (;;) {
      int current = getState();
      if (current == 0 || compareAndSetState(current, 0))
        returncurrent; }}}Copy the code

First, Sync initialization, internally calling setState and passing permitting. As we know, State in AQS is actually the value of synchronization State, while permitting of Semaphore represents the number of permits.

GetPermits actually means that the getState method has been called to obtain the thread synchronization status value. The nonfairTryAcquireShared method in Semaphore actually constructs the tryAcquireShared call in NonfairSync

What is NonfairSync? A look at the JDK source shows that there is.

So what do FairSync and NonfairSync stand for here? Why are there these two classes?

In fact, Semaphore, like ReentrantLock, is “fair” and “unfair”; by default, Semaphore is an unfair Semaphore

Semaphore’s unfairness means that it does not guarantee the order in which threads are granted permissions. Semaphore allocates a license to the thread calling Acquire before the thread waits, and the thread with this license automatically places itself at the head of the thread wait queue.

When this parameter is true, Semaphore ensures that any call to Acquire methods will obtain permissions in first-in, first-out order.

final int nonfairTryAcquireShared(int acquires) {
  for (;;) {
    // Get the synchronization status value
    int available = getState();
    // The value of state - the semaphore that the current thread needs to fetch (usually defaults to -1), only
    // Remaining > 0 indicates that remaining can be obtained.
    int remaining = available - acquires;
    // Check whether the value is less than 0. If the value is less than 0, the value cannot be obtained
    // Use CAS to check whether the memory value is consistent with the synchronization status value, and then update the synchronization status value -1
    if (remaining < 0 ||
        compareAndSetState(available, remaining))
      returnremaining; }}Copy the code

The main difference between NonfairSync and FairSync is the tryAcquireShared method.

In the NonfairSync version, it does not care if there are queue permissions in the current wait queue. It directly determines the signal permissions and the feasibility of the CAS method.

In the FairSync version, it first determines if there are permissions to queue, and if there are, it simply fails to fetch.

If the tryAcquireShared method is different from the tryAcquireShared method, then the tryAcquireShared method is different.

Don’t worry, let’s go into the acquire method and find out

As you can see, in acquire approach, will call tryAcquireShared method, according to whether the return value judgment call doAcquireSharedInterruptibly method, The use of more about doAcquireSharedInterruptibly analysis, please refer to the readers of this article

One article to understand CountDownLatch usage and source code!

It is important to note that the acquire method is obstructive, while the tryAcquire method is not.

That is, if the acquire method is called without a license, Semaphore blocks until a license is available. The tryAcquire method returns false if it cannot obtain permission.

The acquireUninterruptibly method not only waits without permission but also does not interrupt. The acquireUninterruptibly method of acquire is either nonblocking or blocking. When using this method, be aware that it is very easy for Java processes to fake death due to massive thread blocking.

There is a release license corresponding to the acquisition license, but the release license does not distinguish between fair release and unfair release. Either way a license is released to Semaphore, and the number of licenses in Semaphore increases.

After calling tryReleaseShared in the figure above to see if it can be released, the releasedShared method in AQS is called to release.

The above release process only releases one license, but in addition, multiple licenses can be released

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

The release process for the later releaseShared is the same as the release process above.

Other Semaphore methods

In addition to the basic acquire and Release methods above, we also need to look at other Semaphore methods. Semaphore’s other methods are few and far between

DrainPermits: Getting and refunding all immediately available permissions is equivalent to setting the memory value to 0 using the CAS method

ReducePermits: And nonfairTryAcquireShared method is similar, except nonfairTryAcquireShared is using CAS to make memory value + 1, while reducePermits is making memory value -1.

IsFair: Whether the Semaphore license is contested fairly or unfairly, the internal implementations are FairSync and NonfairSync.

HasQueuedThreads: Whether any threads are currently blocked due to obtaining Semaphore licenses.

GetQueuedThreads: Returns a collection of threads waiting for permission.

GetQueueLength: Gets the number of queued and blocked threads

I have uploaded six PDFS by myself, and the spread has exceeded 10W + on the Internet. After searching the public account of “Programmer Cxuan” on wechat, I reply to CXuan on the background and get all PDFS. These PDFS are as follows

Six PDF links