What is a Semaphore

A Semaphore, also known as a Semaphore, is a signal used to direct action or give instructions by some agreed means. Corresponding to the usage scenario for Semaphore, is how to concurrently occupy limited common resources.

Consider a realistic scenario — a dining room chair. We make sure that guests never sit in the same chair. Then serve those guests only when the number of chairs available meets the number of guests to be fed in the next batch.

Semaphore features are:

  • non-reentrant
  • Support fair and unfair locks
  • Multiple locks can be applied and released
  • It inherits other features of AQS

If you’re facing a scenario similar to the one above, Semaphore will help.

example

Semaphore is also very simple to use, as illustrated above, a simple program for

public static void main(String[] args) {
    // Suppose there are 20 chairs in the dining room
    Semaphore semaphore = new Semaphore(20 , true);
    Random random = new Random();
    // 10 is the quantity per unit time assumed
    for (int i=0; i<10; i++){
        Thread t = new Thread(){
            @Override
            public void run() {
                try {
                    for(;;) {// Count is how many chairs are occupied by the guests of this list
                        int count = random.nextInt(9) + 1;
                        System.out.println(getName() + " need " + count + " chairs.");
                        // Arrange chairs for guests
                        semaphore.acquire(count);
                        System.out.println(getName() + "Eat, occupy" + count + " chairs.");
                        // It is arranged, assuming the eating time
                        sleep(1000);
                        System.out.println(getName() + "Leave.");
                        // Empty the chairsemaphore.release(count); }}catch (Exception e){
                }
            }
        };
        t.setName("Thread --> "+ i); t.start(); }}Copy the code

The program will continue to execute, will not miss orders, will not appear the number of chairs occupied more than 20 situations.

AQS basis

Semaphore is a shared lock whose implementation depends on AQS. For locks, there are two parts of knowledge, one is how to unlock, and the other is who to assign the lock to. AQS solves the problem of who to assign locks to, so Semaphore can focus on how to unlock them.

Knowing AQS will make it very easy to understand how Semaphore works, and failure to do so will not affect the understanding of this article.

AQS principle can refer to: article to understand AQS

Here, you need to understand how AQS works:

  1. When a lock is requested, that is, a semantically similar method like acquire() is called,AQS will ask the subclass if the lock was successful and continue running if it was. Otherwise, AQS logs the lock request at Node granularity, inserts it into its own maintained CLH queue and suspends the thread
  2. In the CLH queue, only the node closest to the head node that has not unclaimed the lock is eligible to apply for the lock
  3. When the thread is woken up, it attempts to acquire the lock, and continues to suspend if it does not. Get it and continue running
  4. When a thread releases the lock, that is, calls the semantically similar method release(), AQS will ask the subclass if the lock has been unlocked successfully, and if so, AQS will actively invoke the appropriate thread from the CLH queue, in procedures 2 and 3
  5. If you need to wait for a condition to be satisfied before applying for a lock, that is, if a semantically similar method wait() is invoked, AQS will maintain a one-way queue of waiting conditions with Node as the granularity, and suspend the thread represented by Node
  6. When the condition is met, that is, when the semantically similar method signal() is called, wake up the Node at the top of the waiting condition queue and execute 1
  7. Subclasses can maintain the state property of AQS to record the unlock state. AQS also provides the CAS method compareAndSetState() to preempt the update state

Briefly, when AQS assigns a lock, the current thread may be suspended and then woken up to try again, repeating the process until the lock is acquired or the wait is cancelled. Externally, it looks as if the entry method was blocked and restored in the future.

Sync

Seamphore inherits AQS from the internal class Sync and implements the method semantics required by subclasses of AQS. Seamphore uses Sync to do all the work. Sync response and unlock methods are nonfairTryAcquireShared() and tryReleaseShared().

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        // Read the current remaining resources
        int available = getState();
        // Obtain the number of resources left after acquires
        int remaining = available - acquires;
        if (remaining < 0 / * * /||
            // If the CAS fails to update the status, a race condition exists and the loop restarts
            compareAndSetState(available, remaining))
            // Tell AQS the result. If the value is greater than or equal to 0, the lock is successful
            returnremaining; }}Copy the code

The number of acquires represents the number of resources to be acquired by the current thread. For AQS, this can be interpreted as the number of locks required. At ①, it indicates that too many locks have been allocated and not enough locks have been allocated, so the allocation is not successful.

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        // The remaining locks
        int current = getState();
        // Releases a lock and releases the number of remaining locks
        int next = current + releases;
        if (next < current) 
            // A thread that acquired the lock did not release the lock correctly
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            // CAS updates the lock status
            / / 2.
            // tell AQS that the lock was released successfully and that there are more locks to allocate
            return true; }}Copy the code

TryReleaseShared () always succeeds because there is no reason for the release to fail unless it is used incorrectly. When this happens, you should throw an exception directly to avoid further errors.

When instantiating, specify the number of locks.

Sync(int permits) {
    setState(permits);
}
Copy the code

Unfair lock implementation

Semaphore is an unfair lock by default. You can use Semaphore’s instantiation parameters to determine whether Semaphore is fair. Unfair lock implementation to Sync subclass NonfairSync implementation.

    static final class NonfairSync extends Sync {
        NonfairSync(int permits) {
            super(permits);
        }
        
        protected int tryAcquireShared(int acquires) {
            returnnonfairTryAcquireShared(acquires); }}Copy the code

Use Sync directly without further elaboration.

Implementation of fair locks

Fairlock is implemented as FairSync, a subclass of Sync.

static final class FairSync extends Sync {
    FairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        for (;;) {
            // If a Node representing a thread is queued in the CLH queue, tell AQS that the lock failed
            if (hasQueuedPredecessors())
                 return -1;
            // The number of locks currently available
            int available = getState();
            // How many locks are left after acquires
            int remaining = available - acquires;
            if (remaining < 0 /* Same as nonfairTryAcquireShared() */||
                compareAndSetState(available, remaining) /*CAS updates the number of locks, fails to re-enter the loop */)
                // Tell AQS whether the lock was successful or not. >= 0 is regarded as successful
                returnremaining; }}}Copy the code

From the diagram in the AQS Basics section, there is an opportunity to jump the queue when applying for the lock for the first time. The difference between fair and unfair, then, is whether a queue in the CLH queue is ignored when a new thread makes a request. FairSync takes into account the CLH queue, and if there are threads queuing in the CLH queue, they queue up and wait.

conclusion

Semaphore’s entry methods are straightforward enough to Sync, and support AQS features without too much encapsulation, so this part of the code will not be posted.

Semaphore does not support reentrant. If the thread that acquired the lock requests the lock again, it may queue up and hang, or even cause a deadlock. Do not use this method! The simple proof is zero

// Change the for loop in the "Examples" section to the following for(;;) { semaphore.acquire(5); semaphore.acquire(5); sleep(1000); semaphore.release(10); }Copy the code

As a whole, Semaphore is not difficult to understand. The core of Semaphore is that when applying for a lock, CAS determines whether the number of locks is sufficient to apply for the lock, and updates the status of the number of locks when releasing the lock. This also shows the importance of AQS, learn AQS can master the core knowledge of Java concurrency.

ASQ learns the portal