AQS synchronization component CountDownLatch analysis and case

CountDownLatch principle

CountDownLatch is implemented through a counter whose initialization value is the number of threads. Each time a thread completes its task, the value of the counter is reduced by 1. When the counter reaches 0, it indicates that all threads have completed the task, and then the thread waiting on the lock can resume executing the task. CountDownLatch can block threads and ensure that threads continue to execute after meeting certain conditions.

Two typical uses of CountDownLatch

  • A thread waits for n threads to complete execution before starting to run. Initialize the counter of CountDownLatch to N: new CountDownLatch(n). Whenever a task thread finishes executing, the counter will be decremented by 1 CountDownLatch Countdown(), when the counter value becomes 0, the thread of await() on CountDownLatch will be awakened. A typical application scenario is that when starting a service, the main thread needs to wait for multiple components to load before continuing to execute. That is, the number of threads can be defined through initialization.
  • Realize the maximum parallelism of multiple threads starting to execute tasks. Note that parallelism, not concurrency, emphasizes that multiple threads start executing at the same time. Similar to a race, multiple threads are placed at the starting point, waiting for the starting gun to sound, and then start running at the same time. The method is to initialize a shared CountDownLatch object and initialize its counter to 1: new CountDownLatch(1). Multiple threads first CountDownLatch before starting to execute tasks Await(), when the main thread calls countDown(), the counter changes to 0 and multiple threads are awakened at the same time.

Source code analysis

/**
     * The count in the constructor is actually the number of threads to wait for. This value can only be set once,
     *Moreover, CountDownLatch does not provide any mechanism to reset the count value.
     *
     * @param count The number of times {@ link #countDown} must be called before the thread can pass {@ link #await}
     * @throws IllegalArgumentException If the given parameter is less than 0, an exception is thrown
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
/**
     * Causes the current thread to wait until the counter is zero unless the current thread is interrupted
     * 1.If the current count is zero, this method returns immediately
     * 2.If the current count is greater than zero, the current thread will be disabled and dormant for thread scheduling purposes,
     * Until the following may occur:
     *	 The count reached zero because the {@ link #countDown} method was called
     *   Thread waiting was interrupted.
     * @throws If the current thread is interrupted while waiting
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
/**    
	 * You can set the waiting time. After this time, it will be executed without waiting until the counter becomes 0, but
     * The previously given thread will still finish executing
     *
     * @param timeout Waiting time length
     * @param unit Waiting time unit
     * @return 
     * @throws If the current thread is interrupted while waiting
     */
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

The first interaction with CountDownLatch is that the main thread waits for other threads. The main thread must call CountDownLatch. Immediately after starting other threads Await() method. In this way, the operation of the main thread will block on this method until other threads complete their tasks.
The other N threads must reference the locking object because they need to notify the CountDownLatch object that they have completed their tasks. This notification mechanism is through CountDownLatch Countdown() method; Every time this method is called, the count value initialized in the constructor is reduced by 1. Therefore, when N threads call this method, the value of count is equal to 0, and then the main thread can resume executing its own tasks through the await() method.
be careful:

  • Constructor of CountDownLatch countdownlatch = new CountDownLatch (7)// 7 indicates the number of threads that need to wait for execution to complete.
  • After the execution of each thread, you need to execute countdownlatch Countdown() method, otherwise the counter will not be accurate;
  • Countdownlatch. Is executed only after all threads are executed Code after await();
  • CountDownLatch blocks the main thread;

Usage example of CountDownLatch

@Slf4j
public class CountDownLatchExample1 {
    /**
     * Number of threads
     */
    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                } finally {
                    // Indicates that a request has been completed
                    countDownLatch.countDown();
                }
            });
        }
        //Causes the current thread to wait until the counter is zero unless the current thread is interrupted
        countDownLatch.await();
        //The 200 requests will not be executed until they are processed
        log.info("finish");
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        // Simulate the time-consuming operation of the request
        Thread.sleep(100);
        log.info("{}", threadNum);
        Thread.sleep(100);
    }
}

In the above code, we have defined the number of requests as 200. After the 200 requests are processed, the system out. println(“finish”);.

//You can set the waiting time. After this time, it will execute. It will not wait until the counter becomes 0, but the previously given thread will still execute
countDownLatch.await(20, TimeUnit.MILLISECONDS);

The rest of the above code is the same as the first code. It uses the wait setting to wait for a certain time before continuing to execute the method.

/**
 * CountDownLatch Simulate concurrent invocation of multiple tasks
 *
 * @author zjq
 */
@Slf4j
public class CountDownLatchExample3 {

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2) {
            @Override
            public void await() throws InterruptedException {
                super.await();
                log.info("The content executed by the main thread after the execution of other threads");
                log.info("threadName:{},", Thread.currentThread().getName() + " count down is ok");
            }
        };

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    log.info(Thread.currentThread().getName() + "Task completed");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //Counter minus 1
                    countDownLatch.countDown();
                }
            }
        }, "thread111");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    log.info(Thread.currentThread().getName() + "Task completed");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //Counter minus 1
                    countDownLatch.countDown();
                }
            }
        }, "thread222");


        thread1.start();
        thread2.start();

        countDownLatch.await();
        log.info("====everything is end====");
    }

}

The above code execution output results:

[thread111] INFO com.zjq.concurrency.aqs.CountDownLatchExample3 - thread111 Task completed
[thread222] INFO com.zjq.concurrency.aqs.CountDownLatchExample3 - thread222 Task completed
[main] INFO com.zjq.concurrency.aqs.CountDownLatchExample3 - The content executed by the main thread after the execution of other threads
[main] INFO com.zjq.concurrency.aqs.CountDownLatchExample3 - threadName:main count down is ok,
[main] INFO com.zjq.concurrency.aqs.CountDownLatchExample3 - ====everything is end====
/**
 * CountDownLatch After simulating the concurrent execution of multiple tasks, wait for the main thread to issue a command and perform subsequent operations at the same time
 *
 * @author zjq
 */
@Slf4j
public class CountDownLatchExample4 {

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    //All threads are blocked here, waiting for the main thread command
                    log.info(Thread.currentThread().getName() + "Ready!!");
                    countDownLatch.await();
                    log.info("[" + Thread.currentThread().getName() + "]" + "Start execution");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        // The main thread is ready to issue orders
        Thread.sleep(2000);
        log.info(Thread.currentThread().getName() + "Give orders and rush!!");
        // Main thread: Execute command
        countDownLatch.countDown();
    }

}

Achieve maximum parallelism output results:

[Thread-3] INFO com.zjq.aqs.CountDownLatchExample4 - Thread-3 Ready!!
[Thread-0] INFO com.zjq.aqs.CountDownLatchExample4 - Thread-0 Ready!!
[Thread-2] INFO com.zjq.aqs.CountDownLatchExample4 - Thread-2 Ready!!
[Thread-1] INFO com.zjq.aqs.CountDownLatchExample4 - Thread-1 Ready!!
[Thread-4] INFO com.zjq.aqs.CountDownLatchExample4 - Thread-4 Ready!!
[main] INFO com.zjq.aqs.CountDownLatchExample4 - main Give orders and rush!!
[Thread-3] INFO com.zjq.aqs.CountDownLatchExample4 - [Thread-3]Start execution
[Thread-1] INFO com.zjq.aqs.CountDownLatchExample4 - [Thread-1]Start execution
[Thread-0] INFO com.zjq.aqs.CountDownLatchExample4 - [Thread-0]Start execution
[Thread-2] INFO com.zjq.aqs.CountDownLatchExample4 - [Thread-2]Start execution
[Thread-4] INFO com.zjq.aqs.CountDownLatchExample4 - [Thread-4]Start execution

Insufficient CountDownLatch

CountDownLatch is one-time. The counter value can only be initialized once in the construction method. After that, there is no mechanism to set the value again. When CountDownLatch is used, it cannot be used again.

CountDownLatch application scenario

(1) Achieve maximum parallelism: sometimes we want to start multiple threads at the same time to achieve maximum parallelism. For example, we want to test a singleton class. If we create a CountDownLatch with an initial count of 1 and let all threads wait on the lock, we can easily complete the test. We only need to call the countDown() method once to make all waiting threads resume execution at the same time.
(2) Wait for n threads to complete their tasks before execution: for example, the application startup class should ensure that all n external systems have been started and run before processing user requests.
(3) Deadlock detection: a very convenient use scenario is that you can use n threads to access shared resources. The number of threads in each test phase is different, and try to generate deadlock.

Keywords: Java CountDownLatch

Added by Fataqui on Fri, 10 Dec 2021 16:26:59 +0200