preface

Semaphore is a counting Semaphore. Semaphore manages a range of licenses. Each acquire method blocks until a license is available and then takes a license; Each release method adds a license, which may free a blocking acquire method. However, there is no actual license object, Semaphore just maintains a number of licenses available. Semaphore maintains the number of threads currently accessing itself and provides a synchronization mechanism. Semaphore can be used to control the number of threads accessing resources at the same time, for example, to implement the number of concurrent accesses allowed for a file.

1 The main method of Semaphore

Semaphore(int permits): constructor that creates a count Semaphore with a given number of permits and sets it to unfair Semaphore.

Semaphore(int permits, Boolean fair): constructor that creates a count Semaphore with a given number of permits and sets it to a fair Semaphore when fair equals true.

Void acquire(): The current thread tried to block to acquire 1 license. This process is blocked, waiting for a license until one of the following things happens:

  • If the current thread has one available license, it stops waiting and continues.
  • If the current thread is interrupted, InterruptedException is thrown and execution stops waiting.

Void acquire(int n): Acquires a given number of permissions from this semaphore and blocks the thread until those permissions are provided. The current thread is trying to block multiple licenses.

This process is blocked, waiting for a license until one of the following things happens:

  • If the current thread has obtained n available licenses, it stops waiting and continues.
  • If the current thread is interrupted, InterruptedException is thrown and execution stops waiting.

Void release(): releases a license and returns it to the semaphore.

Void release(int n): releases N permissions.

Int availablePermits() : the number of permits currently available. Void acquierUninterruptibly(): The current thread is trying to block to obtain 1 license (non-interruptible).

This process is blocked, waiting for a license until one of the following things happens:

  • If the current thread has one available license, it stops waiting and continues.

Void acquireUninterruptibly(permits): Permits that the current thread attempts to block.

This process is blocked, waiting for a license until one of the following things happens:

If the current thread has obtained n available licenses, it stops waiting and continues. Boolean tryAcquire() The current thread tried to acquire 1 license.

This procedure is non-blocking; it is just a one-time attempt at method invocation.

If the current thread has one available license, it stops waiting, continues execution, and returns true.

If the current thread does not obtain this license, the wait is stopped and execution continues, returning false.

Boolean tryAcquire(Permits) : Current thread attempts to obtain multiple permits.

This procedure is non-blocking; it is just a one-time attempt at method invocation.

If the current thread has an permits available, the wait stops, execution continues, and true is returned.

If the current thread does not have permits, the wait is stopped and execution continues, and false is returned.

Boolean tryAcquire(timeout,TimeUnit) : The current thread blocks an attempt to acquire a license within a specified time.

This process is blocked, waiting for a license until one of the following things happens:

  • If the current thread has obtained an available license, it stops waiting and continues executing, returning true.
  • If the current thread waits for a timeout, the wait is stopped and execution continues, and false is returned.
  • If the current thread is interrupted during timeout, InterruptedException is thrown once and execution stops waiting.

Boolean tryAcquire(Permits,timeout,TimeUnit) : A block attempt has been made to obtain permits with a time limit.

This process is blocked, waiting for a license until one of the following things happens:

  • With an available permits, the current thread stops waiting, continues, and returns true.
  • If the current thread waits for a timeout, the wait is stopped and execution continues, and false is returned.
  • If the current thread is interrupted during timeout, InterruptedException is thrown once and execution stops waiting.

2 Example Explanation


public class SemaphoreTest {

    private static final Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        Executor executor = Executors.newCachedThreadPool();

        String[] name = {"Jack"."Pony"."Larry"."Martin"."James"."ZhangSan"."Tree"};

        int[] age = {21.22.23.24.25.26.27};

        for(int i=0; i<7; i++) { Thread t1=newInformationThread(name[i],age[i]); executor.execute(t1); }}private static class InformationThread extends Thread {
        private final String name;
        private final int age;

        public InformationThread(String name, int age) {
            this.name = name;
            this.age = age;
        }


        @Override
        public void run(a) {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName()
                        + "Hello everyone, I am." + name + "My year" + age +
                        "The current time range is:" + System.currentTimeMillis());
                Thread.sleep(1000);
                System.out.println(name + "Ready to release license, current time is:" + System.currentTimeMillis());
                System.out.println("The number of licenses currently available is:" + semaphore.availablePermits());
                System.out.println("Are there any threads waiting for licenses:" + semaphore.hasQueuedThreads());
                System.out.println("Queue length (number of threads) waiting for license:" + semaphore.getQueueLength());
                semaphore.release();
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

- the thread pool - 1-1: hello, everyone, I am Jack I this year 21 of the current period is: 1543498535306 - thread pool - 1-3: hello, I'm Larry I this year 23 of the current period as follows: 1543498535306 POOL-1-thread2: Hello, everyone. I am Pony and I am 22 this year. The current time is: 1543498535306 Pony needs to prepare the release license. 1543498536310 The number of available licenses is: 0 Larry is ready to release the license. The current time is: 1543498536310 Whether there are threads waiting for licenses: true The number of available licenses is: 0 Whether there are threads waiting for licenses: True Queue length (number of threads) : 4 Queue length (number of threads) : 4 Number of licenses currently available: 0 Pool-1-thread-4: Hello, I'm Martin. I'm 24 this year. 1543498536311 If any thread is waiting for a license: true pool-1-thread-5: Hello, I'm James. 2 Pool-1-thread6: Hi, everyone, I am ZhangSan, I am 26 this year, the current time is: 1543498536312, James needs to prepare the release license, the current time is: 1543498537315, Martin needs to prepare the release license, the current time is: 1543498537315, Martin needs to prepare the release license, the current time is: 1543498537315 The number of licenses currently available is as follows: 0 Whether there are threads waiting for licenses: true Queue length (number of threads) waiting for licenses: 1 Number of licenses currently available is as follows: 0 Whether there are threads waiting for licenses: False pool-1-thread-7: hello, I am Tree 27 this year current time range: 1543498537316 Queue length (number of threads) : 0 ZhangSan is ready to release the license, the current time is: 1543498537317 The number of available licenses is as follows: 1 Whether there are threads waiting for licenses: false Length of queues waiting for licenses (number of threads) : 0 Tree Ready to release licenses, the current time is as follows: 1543498538319 The number of available licenses is as follows: 2 Whether there are threads waiting for licenses: false Length of queue (number of threads) waiting for licenses: 0Copy the code

The above is an unfair Semaphore. Change the statement that creates the Semaphore object to the following:


private static final Semaphore semaphore=new Semaphore(3.true);
Copy the code

Pool-1-thread-1: Hello, I'm Jack. Pool-1-thread-3: Hello, I'm Larry. Pool-1-thread-1: Hello, I'm Jack. 1543498810564 POOL-1-thread-2: Hello, everyone, I am Pony 22 this year. The current time is: 1543498810563. Jack is preparing to release the license. 0 Specifies the number of threads in the queue that are waiting for a license: true Specifies the number of threads in the queue that are waiting for a license: 4 Pool-1-thread-4: 1543498811568 Number of available licenses: 0 Pony Ready to release licenses, current time: 1543498811568 Whether there are threads waiting for licenses: true Number of available licenses: 0 Length of queues waiting for licenses (number of threads) : The number of threads waiting for a license is 1543498811568. The number of threads waiting for a license is 1543498811568. The number of threads waiting for a license is 1543498811568. 2 Pool-1-thread6: Hello, I'm ZhangSan this year 26 current time range: 1543498811568 Martin is preparing to release license, current time range: 1543498812566 0 if there is a thread waiting for a license: true If there is a thread waiting for a license: true If there is a thread waiting for a license: 1 Pool-1-thread-7: 1543498812572 The number of available licenses is as follows: 0 Whether there is a thread waiting for a license: false Length of queue waiting for a license (number of threads) : 0 ZhangSan is preparing to release the license. The current time is as follows: 1543498812572 The number of available licenses is as follows: 1 Whether there are threads waiting for licenses: false Queue length (number of threads) waiting for licenses: 0 Tree Ready to release licenses, current time: 1543498813568 The current number of licenses available is: 2 Whether there are threads waiting for licenses: False Length of queue (number of threads) waiting for license: 0Copy the code
Implement the singleton pattern

Modify the create semaphore object statement as follows:


private static final Semaphore semaphore=new Semaphore(1);
Copy the code

Run the program and the result is as follows:


Pool-1-thread-1: Hello, I'm Jack. The current time range is 1543499053898. The current time range is 1543499054903. Queue length (number of threads) : 6 Pool-1-thread-2: Hello everyone, I am Pony, I am 22 this year, the current time is: 1543499054904 Pony is ready to release the license, the current time is: 1543499055907 The number of available licenses is: 0 Whether there are threads waiting for licenses: true Number of threads waiting for licenses: 5 Pool-1-thread-3: The current number of available licenses is: 0 If there are threads waiting for licenses: true The length of the queue waiting for licenses (number of threads) : The current time range is 1543499056909. Martin is preparing to release the license. The current time range is 1543499057913. True Specifies the number of threads in the queue waiting for a license: 3 Pool-1-thread-5: Hello, I'm James. I'm 25 years old. 1543499058914 The number of available permissions is as follows: 0 Whether there is a thread waiting for a license: true Length of queue waiting for a license (number of threads) : 2 Pool-1-thread6: Hello, I am Tom. I am 26 this year. 1543499058915 ZhangSan is ready to release license, the current time is: 1543499059919 The current number of licenses available is: 0 Whether there are threads waiting for license: true The length of queue waiting for license (number of threads) : 1 pool-1-thread-7: hi, I'm Tree 27 this year the current time range is: 1543499059919 The current time range is: 1543499060923 The current number of available licenses is: 0 False Length of queue (number of threads) waiting for license: 0Copy the code

As can be seen above, if the given number of permits is set to 1, it is like a singleton mode, i.e. a single parking space, only one car can enter, and then the next car can enter after this car comes out.

3 Source code Analysis

Semaphore has two modes, fair mode and unfair mode. Fair mode is that the order of calling acquire is the order of obtaining license, following THE FIFO; The non-fair mode is preemptive, meaning it is possible for a new thread to acquire a license just as it is released, with waiting threads ahead.

A constructor

Semaphore has two constructors, as follows:


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

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

Start with getting a license, and look at the implementation in unfair mode. Start with the Acquire method, which has several overloads, but mainly the following one


  public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }
Copy the code

From the above you can see, call the Sync acquireSharedInterruptibly method, this method in the superclass AQS, as follows:


    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted()) // If the thread is interrupted, throw an exception
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0) // Failed to get permission. Thread added to wait queue
            doAcquireSharedInterruptibly(arg);
    }
Copy the code

If the AQS subclass wants to use shared mode, it needs to implement the tryAcquireShared method.


protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
Copy the code

This method calls the nonfairTyAcquireShared method in its parent class as follows:


final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                // Obtain the number of remaining licenses
                int available = getState();
                // Count the number of permissions given
                int remaining = available - acquires;
                // Return if the number of permissions is insufficient or can be reset
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    returnremaining; }}Copy the code

As you can see from the above, the return value is less than 0 only when there are insufficient permits, and the remaining number of permits is returned, which explains why subsequent threads will block if there are insufficient permits. Having looked at unfair acquisition, let’s look at fair acquisition. The code is as follows:


protected int tryAcquireShared(int acquires) {
            for (;;) {
                // If a thread is waiting, return -1
                if (hasQueuedPredecessors())
                    return -1;
                // After the same as not fair
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    returnremaining; }}Copy the code

If a thread is waiting in the queue, it will enter the queue. If a thread is waiting in the queue, it will enter the queue. Unlike NonfairSync, where you try first and just might get a permit to jump the queue. After looking at the license, look at the release license.

Release the license

There are several overloaded methods for releasing permissions, but they all call the following method with arguments,


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

ReleaseShared method in AQS, as follows:


public final boolean releaseShared(int arg) {
        // If changing the license number succeeds
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
Copy the code

AQS subclasses implementing shared mode need to implement the tryReleaseShared class to determine whether the release is successful, the implementation is as follows:


protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                // Get the current license number
                int current = getState();
                // Calculate the number of collected
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                //CAS succeeded in changing the number of licenses, returning true
                if (compareAndSetState(current, next))
                    return true; }}Copy the code

As you can see above, once CAS has successfully changed the number of licenses, the doReleaseShared() method is called to release the blocked thread.

Reduced number of permits

Semaphore also has a method of reducing the number of licenses, which can be used to reduce licenses when resources run out and can no longer be used. The code is as follows:


protected void reducePermits(int reduction) {
        if (reduction < 0) throw new IllegalArgumentException();
        sync.reducePermits(reduction);
    }
Copy the code

As can be seen, entrusted to Sync, Sync’s reducePermits method is as follows:


  final void reducePermits(int reductions) {
            for (;;) {
                // Get the current number of remaining licenses
                int current = getState();
                // Get the number of licenses after the reduction
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                // If the CAS change succeeds
                if (compareAndSetState(current, next))
                    return; }}Copy the code

As you can see above, it is CAS that changes the state variable in the AQS because it represents the number of licenses.

Obtain the number of remaining licenses

Semaphore can also take the remaining license quantity all at once, using the drain method as follows:


public int drainPermits(a) {
        return sync.drainPermits();
    }
Copy the code

Sync is implemented as follows:


 final int drainPermits(a) {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    returncurrent; }}Copy the code

As you can see, CAS sets the number of permits to 0.

conclusion

Semaphore is a Semaphore used to manage a group of resources. The internal sharing mode is based on AQS. The status of AQS indicates the number of licenses. If the number of licenses is insufficient, the thread will be suspended. Once a thread releases a resource, it is possible to wake up the threads in the waiting queue to continue executing.