What does countdown latch do?

The article is short. I haven't explained some top-level methods of AQS, such as releaseShared, because these are the categories of AQS. You can see another article about AQS—— AQS.

CountDownLatch is generally referred to as a "counter". Its function is to count until the number reaches a certain point. It can be used for functions such as process control. A large process is divided into multiple sub processes, and then the large process does not move until all the sub processes are completed (the sub processes should be independent of each other unless the correlation between the two processes can be well controlled). After all the sub processes are completed, the large process starts to operate.

  very abstract, small problem. The following two sections may enable you to understand the usage and internal implementation of CountDownLatch.

1. Use of countdownlatch

  suppose we want to start a fund-raising project of 3 yuan, and limit everyone to donate only 1 yuan at a time. When we raise 3 yuan, we will donate the money to myself immediately. If you still want to donate after collecting it, I will tell you that the project has been completed. I won't accept your one yuan. Go and buy ice cream by yourself; If I don't get it all together, my collection box will be hanging here all the time. This scenario can be simulated with CountDownLatch.

  just look at the demo:

public static void main(String[] args) throws InterruptedException {
    
    // Fund raising project = ========> start, target 3 yuan
    CountDownLatch countDownLatch = new CountDownLatch(3);
    ThreadPoolExecutor executor = ThreadPoolProvider.getInstance();
    executor.execute(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
            System.err.println("Zhang 1 is going to donate a dollar");
            countDownLatch.countDown();
            System.err.println("Zhang 1 donated a dollar");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    });

    executor.execute(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
            System.err.println("Zhang 2 is going to donate a dollar");
            countDownLatch.countDown();
            System.err.println("Zhang 2 donated a dollar");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    executor.execute(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
            System.err.println("Zhang 3 is going to donate a dollar");
            countDownLatch.countDown();
            System.err.println("Zhang 3 donated a dollar");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    System.err.println("After my project started, I waited here for donations. I won't go if it's less than 3 yuan");
    countDownLatch.await();
    System.err.println("3 Get the money and run straight away");

    executor.shutdown();
}

 

Result diagram:


From this result, em, you can see several points for attention in the use of countDownLatch:

  1. The thread that calls countDownLatch's await() method will block until three dollars are collected
  2. Follow CyclicBarrier Different, after counting, it will not block, but directly execute the next operation
  3. Every time you call the countDown() method, you will donate a dollar (count once), and the thread that calls the await() method will no longer block.

  in addition, in the above code, the information is printed after the countDown method to verify that the countDown method will not block the current thread, and the execution results may not be in order as shown in the above figure. For example, the following results may appear:


  because after the last countDown, the thread where await is located is no longer blocked, and just in time for JVM thread scheduling, the above results will appear.

2. Internal implementation of countdownlatch

  the usage of countdown latch has been mentioned just now. It's not difficult to use. Let's take a look at how it is implemented internally and how it is not followed after counting CyclicBarrier What about the same blockage?

    let's first look at the constructor. CountDownLatch has only one constructor, as follows:

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

  The only thing to do is to initialize the internal object sync (verification can't be counted as one thing?). Let's see what has been initialized

// Does the variable sync look familiar?
private final Sync sync;

// The internal class Sync is also a product of AQS
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

	// The construction method is to set the AQS state value
    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }

    /*
     * You can see that countDownLatch uses AQS sharing mode
     * Obtain resource methods. Positive numbers indicate success and negative numbers indicate failure
     */
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    // Release method
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            // state, which indicates the remaining count quantity in countDownLatch. If it is 0, it indicates that it can be obtained, that is, the await method is no longer blocked
            int c = getState();
            if (c == 0)
                return false;
            // And quantity remaining after this counting
            int nextc = c-1;
            // CAS set remaining count
            if (compareAndSetState(c, nextc))
                // ==0 means that the lock is released. After that, the value of state will always be 0, which means that the subsequent await methods will no longer be blocked
                return nextc == 0;
        }
    }

What information can I get from the code above

1. The function of the countdownlatch constructor count parameter is to set its internal AQS state. Assuming count is 3, the AQS state will be reduced by 1 every time countDown is performed. When it is reduced to 0, the await method will no longer block. Note that the await method will no longer block at this time, no matter how many times you adjust it.

2. AQS sharing mode implemented by Sync in countdownlatch (as can be seen from tryrereleaseshared method)

  now I have a general impression of the CountDownLatch. Next, let's look at the most important await and countDown methods.

2.1 await method

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

  The top-level method of AQS is called directly, and then the AQS module is entered

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // Obtain resources, return directly after success, and execute the following method if failure (enter the synchronization queue)
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

For a brief explanation, this method means to call the tryAcquireShared method to try to obtain resources. A negative number returned by the method indicates failure, and a positive number indicates success; If it fails, it will enter the synchronization queue (i.e. blocking). See the details below AQS A detailed explanation of.
  in other words, the key point is the tryAcquireShared method. This method has been explained above and will be put again here. The method logic is very simple. If state = 0 (i.e. counting is completed), it succeeds, otherwise it fails.

 

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

  okay, the whole process of await method is roughly: try to obtain resources, block if it fails, and continue the operation of the current thread successfully. When will it fail? In state= 0, and the value of the state variable has been given in the constructor, which needs to be reduced through the countDown method.

2.2 countDown

  since this method is so important, let it start its performance.

 

public void countDown() {
    sync.releaseShared(1);
}

Similarly, directly call the top-level method of AQS to release resources.

public final boolean releaseShared(int arg) {
    // If the resource is released, the waiting thread in the synchronization queue is awakened
    if (tryReleaseShared(arg)) {
        // Aftercare operation
        doReleaseShared();
        return true;
    }
    return false;
}

  The key method is resource control - tryrereleaseshared. The code is as follows (also shown above):

 

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        /* 
         * state In countDownLatch, it indicates the remaining count quantity
         * If it is 0, it means that it can be obtained, that is, the await method is no longer blocked
         */
        int c = getState();
        // This means that if the resource has been released, it cannot be released again. The successful release code is in the last line
        if (c == 0)
            return false;
        // And quantity remaining after this counting
        int nextc = c-1;
        // CAS set remaining count
        if (compareAndSetState(c, nextc))
            // ==0 means that the lock is released. After that, the value of state will always be 0, which means that the subsequent await methods will no longer be blocked
            return nextc == 0;
    }
}

Here we can see the fog of the countDown method. Every call to the countDown method is equivalent to calling the tryrereleaseshared method. If the current resource has not been released, state-1 will be used to judge whether it is 0. If it is 0, it means that the resource is released and the thread of the await method will be awakened. Otherwise, it will only update the state value.

  sort out the whole CountDownLatch process.

1. Create a CountDownLatch and assign a numerical value. This value indicates the number of times to be counted. Count every countDown

2. When the main thread calls the await method, it means that the counter cannot be moved until it is completed. The internal implementation of the await method depends on the internal AQS. When the await method is called, it will try to obtain resources. The success condition is state=0, that is, it can succeed only after countDown is given by the constructor. If it fails, the current thread will sleep.

3. When the child thread calls the countDown method, each call will release the internal state-1. When the state is 0, the await method will no longer block (even if it is called again)

 

3. Summary

  if you understand AQS Not only CountDownLatch, but also other derivatives such as ReentrantLock can be easily understood. It doesn't matter if you don't understand it. This article should give you a general outline of the internal implementation of CountDownLatch.

  to sum up, CountDownLatch has three points: constructor value, await and countDown. The value of the constructor indicates the number of counts. Each countDown will reduce the count by one. When it is reduced to 0, the thread where the await method is located will no longer be blocked.

Tags: Java Concurrent Programming

Posted on Fri, 03 Dec 2021 13:37:48 -0500 by NJordan72