How CountDownLatch works and use cases

Posted by ruttiger on Fri, 28 Feb 2020 12:55:26 +0100

Definition: a multi-functional synchronization tool that allows one or more threads to wait until a set of operations performed in other threads are completed. Literally translates to "countdown latch.".

Function: initialize CountDownLatch with the given count. Because of the call to the countCount method, until the current count reaches zero, the await method will block, after which all freed threads will be released, and any subsequent await call will return immediately. This is a one-time phenomenon - the count cannot be reset. If you need a version to reset the count, consider using the CyclicBarrier.
CountDownLatch is a multi-functional synchronization tool.

  1. CountDownLatch is initialized as a simple on / off lock or door: all calling threads wait at the door until the thread called countDown opens.
  2. CountDownLatch initialized to N can be used to make a thread wait until N threads complete an action or an action has completed N times.

 

Core approach:

public void countDown();

Reduce the number of latches, and if the number reaches zero, release all waiting threads.
If the current count is greater than zero, it is decremented. If the new count is zero, all waiting threads are re enabled for thread scheduling.
If the current count is equal to zero, nothing happens.

public void await() throws InterruptedException;

Causes the current thread to wait until the latch decrements the count to zero, unless the thread is interrupted.
If the current count is zero, this method returns immediately.
If the current count is greater than zero, the current thread is disabled for thread scheduling purposes and is in a sleep state until either of the following occurs:

  • The count reached zero due to a call to the countDown method.  
  • Or, some other threads interrupt the current thread.

If the current thread:

  • Its interrupt state has been set when entering this method;
  • Or, interrupted while waiting,

Then, InterruptedException is thrown and the interrupt state of the current thread is cleared.

Throw out:
InterruptedException - if the current thread is interrupted while waiting

Application scenario 1: decompose a task into n subtasks for processing. The problem is divided into N parts. Each part is described by Runnable, which executes the part and decrements the count on the latch, then queues all Runnable to the executor. When all the child parts are completed, the coordinating thread will be able to pass the wait. (use CyclicBarrier when threads have to decrement the count repeatedly in this way.)

Official example code:

class Driver2 { // ...
   void main() throws InterruptedException {
     CountDownLatch doneSignal = new CountDownLatch(N);
     Executor e = ...

     for (int i = 0; i < N; ++i) // create and start threads
       e.execute(new WorkerRunnable(doneSignal, i));

     doneSignal.await();           // wait for all to finish
   }
 }

 class WorkerRunnable implements Runnable {
   private final CountDownLatch doneSignal;
   private final int i;
   WorkerRunnable(CountDownLatch doneSignal, int i) {
     this.doneSignal = doneSignal;
     this.i = i;
   }
   public void run() {
     try {
       doWork(i);
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
   }

   void doWork() { ... }
 }

Scenario 2: This is a pair of classes, in which a group of worker threads use two reciprocal latches:

  • The first is the start signal, which prevents any worker from moving on until the driver is ready for them.
  • The second is the completion signal. The driver can wait for all workers to complete the operation.

Official example code:

 class Driver { // ...
   void main() throws InterruptedException {
     CountDownLatch startSignal = new CountDownLatch(1);
     CountDownLatch doneSignal = new CountDownLatch(N);

     for (int i = 0; i < N; ++i) // create and start threads
       new Thread(new Worker(startSignal, doneSignal)).start();

     doSomethingElse();            // don't let run yet
     startSignal.countDown();      // let all threads proceed
     doSomethingElse();
     doneSignal.await();           // wait for all to finish
   }
 }

 class Worker implements Runnable {
   private final CountDownLatch startSignal;
   private final CountDownLatch doneSignal;
   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
   }
   public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
   }

   void doWork() { ... }
 }

 

Let's talk about the case code implementation of scenario 1:

  • First, a shared CountDownLatch is initialized, and the counter value is initialized to N;
  • After starting all the worker threads, call the await() method and wait;
  • When each thread completes a task, call the countDown() method inside the task thread to reduce the counter value N by 1;
  • When the counter N is reduced to 0, it means that all the tasks of the sub threads have been completed and no longer wait.

Example code:

import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    private static class Worker implements Runnable {
        private CountDownLatch latch;
        List<String> classIds;
        List<String> result;

        Worker(CountDownLatch latch, List<String> classIds, List<String> result) {
            this.latch = latch;
            this.classIds = classIds;
            this.result = result;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " execute task. ");
                this.result.addAll(classIds);
                System.out.println(Thread.currentThread().getName() + " finished task. ");
            } finally {
                latch.countDown();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        List<String> ids = getTestList();

        CountDownLatch latch = new CountDownLatch(5);
        List<String> result = Collections.synchronizedList(new ArrayList<>(100));

        for (int i = 0; i < 5; i++) {
            Worker worker = new Worker(latch, ids, result);
            Thread thread = new Thread(worker, "Worker-Thread-" + i);
            thread.start();
        }

        System.out.println("Main-Thread await. ");
        latch.await();
        System.out.println("Main-Thread finishes await. ");
        System.out.println(result);
    }

    private static List<String> getTestList() {
        int SIZE = 20;
        List<String> ids = Lists.newArrayListWithCapacity(SIZE);
        for (int i = 0; i < SIZE; i++) {
            ids.add(i + "");
        }
        return ids;
    }

}

 

Published 8 original articles, won praise 3, visited 2366
Private letter follow

Topics: Java Google Programming