Follow the wechat official account JavaStorm

Semaphore is now commonly translated as “Semaphore.” Conceptually, Semaphore maintains a set of “credentials” that the thread that has acquired the credentials can access the resource, which can be released after completion. We can use the Semaphore to limit the number of concurrent threads that can access a particular resource.

Just like the parking lot in real life, when there is an empty space to put the car into, otherwise they can only wait, out of the car will release the certificate.

Semaphore model

It can be easily summarized as: one counter, one wait queue, and three methods. In the semaphore model, counters and wait queues are transparent and can only be accessed through the three methods provided by the semaphore model: init(), acquire(), and Release ().

  • Init (): Sets the initial value of the counter and initializes the number of credentials. It’s the number of Spaces in the parking lot.
  • Acquire () : the value of the counter is reduced by 1; If the value of the counter is less than 0, the current thread is blocked and placed in a wait queue, otherwise the current thread can continue.
  • Release () : counter incremented by 1; If the counter value is less than or equal to 0, a thread in the wait queue is woken up and removed from the wait queue.

The init(), acquire(), and release() methods mentioned here are all atomic, and this atomicity is guaranteed by the implementation of the semaphore model. Inside the Java SDK, signal model is by Java. The util. Concurrent. The Semaphore, Semaphore this class to ensure that the three methods are atomic operations.

Easy to understand through a simplified version of the signal model code:

public class Semaphore {
    / / counter
    private int count;
    // Save the queue for the thread
    private Queue queue;

    /** * Initializes the counter *@param count
     */
    public Semaphore(int count) {
        this.count = count;
    }

    /** * get the certificate */
    public void acquire(a){
        this.count--;
        if(this.count<0) {// Insert the current thread into the wait queue
            // Block the current thread}}/** * release credentials */
    public void release(a){
        this.count++;
        if(this.count >= 0) {
            // Remove a thread T from the wait queue
            // Wake up thread T}}}Copy the code

Use of semaphores

We learned about the semaphore model principles above, and now look at how they can be used in real world scenarios. Here we use the accumulator example to illustrate the use of semaphores. In the accumulator example, the count++ operation is a critical section that allows only one thread to execute, which means mutual exclusion is guaranteed.

public class TestSemaPhore {
    private static int count;
    // Initialize the semaphore to 1
    private static final Semaphore semaphore = new Semaphore(1);

    public static void addOne(a) throws InterruptedException {
        // Use the semaphore to ensure that it is mutually exclusive and only one thread enters
        semaphore.acquire();
        try {
            count++;
        } finally{ semaphore.release(); }}public static int getCount(a) {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        // Simulates the simultaneous access of ten threads
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    addOne();
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        countDownLatch.countDown();
        TimeUnit.SECONDS.sleep(3);
        intcount = getCount(); System.out.println(count); }}Copy the code

Let’s analyze how semaphores are guaranteed to be mutually exclusive.

Suppose two threads T1 and T2 access addOne() at the same time, when they both call Semaphore.acquire (); Since this is an atomic operation, only one thread can reduce the semaphore counter to 0, while the other thread T2 can reduce the counter to -1. The value of the corresponding thread T1 counter is 0, satisfying the value greater than or equal to 0, so thread T1 will continue to execute; For thread T2, the semaphore counter has a value of -1, less than 0, and as described in our previous semaphore model Acquire (), thread T2 will be blocked and queued. So at this point only thread T1 enters the critical section and executes count++.

The current semaphore counter value is -1. When thread T1 performs semaphore.release(), the counter +1 becomes 0, which is less than or equal to 0. According to the definition of the model, T2 in the waiting queue will be awakened at this moment. Therefore, T2 gets the opportunity to enter the code collar interception after T1 completes the execution of the critical section code, thus ensuring mutual exclusion.

Implement a current limiter

In the example above we implemented a simple mutex using semaphores. Do you wonder why we need Semaphore when the Java SDK provides Lock? Semaphore also has a feature that Lock can’t easily implement: Semaphore allows multiple threads to access a critical section.

Pooled resources are common, such as connection pools, object pools, thread pools, and so on. The familiar database connection pool, for example, allows multiple threads to use the connection at the same time, and of course other threads to use it until each connection is released.

Now let’s assume that we have a scenario, object pooling requirement, where N objects are created once, and then all threads reuse these N objects, and no other threads are allowed to use them until they are released.

/** * Object pool ** /
public class ObjectPool {
    // Use blocking queues to save object pools
    private final ArrayBlockingQueue<InputSaleMapDO> pool;
    / / semaphore
    private final Semaphore sem;

    /** * Initializes the object pool **@paramSize Pool size */
    public ObjectPool(int size) {
        pool = new ArrayBlockingQueue<>(size);
        sem = new Semaphore(size);
        for (int i = 0; i < size; i++) {
            InputSaleMapDO inputSaleMapDO = new InputSaleMapDO();
            inputSaleMapDO.setId((long) i); pool.add(inputSaleMapDO); }}// Call function with objects from the object pool
    public Long run(Function<InputSaleMapDO, Long> function) throws InterruptedException {
        InputSaleMapDO obj = null;
        sem.acquire();
        try {
            obj = pool.poll();
            return function.apply(obj);
        } finally{ pool.add(obj); sem.release(); }}public static void main(String[] args) throws InterruptedException {
        ObjectPool objectPool = new ObjectPool(2);

        // Simulates the simultaneous access of ten threads
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    objectPool.run(f -> {
                        System.out.println(f);
                        return f.getId();
                    });
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        countDownLatch.countDown();
        TimeUnit.SECONDS.sleep(30); }}Copy the code

Initialize thread pool size 2, we simulate 10 threads, only two threads at a time can allocate object InputSaleMapDO.

After executing the callback, they release the object (through pool.add(obj)) and call the release() method to update the semaphore’s counter. If the value of the counter in the semaphore is less than or equal to 0, it indicates that there is a thread waiting, and the waiting thread is automatically woken up.

thinking

The example above uses ArrayBlockingQueue, which is a thread-safe container, to hold the object pool. Can you use ArrayList instead? Welcome to the background. Also, assuming that the parking space in the parking lot is used as the object pool, can car owners also use Semaphore for parking?