Detailed explanation of the combination of thread pool and CountDownLatch

1, CountDownLatch initial

count down in CountDownLatch means countdown, and latch means latch. The overall meaning can be understood as the countdown bolt, which seems to have the feeling of "three, two, one, sesame open the door". The function of CountDownLatch is the same. When constructing CountDownLatch, you need to pass in an integer n. before the integer "countdown" reaches 0, the main thread needs to wait at the door, and this "countdown" process is driven by each execution thread. Each thread performs a task "countdown" once. To sum up, the function of CountDownLatch is to wait for other threads to complete the task. If necessary, the execution results of each task can be summarized, and then the main thread can continue to execute.

CountDownLatch has two main methods: countDown() and await(). The countdown () method is used to decrease the counter by one, which is generally called by the thread executing the task. The await () method makes the thread calling the method in a waiting state, which is generally called by the main thread. It should be noted here that the countdown () method does not stipulate that a thread can only call it once. When the same thread calls the countdown () method multiple times, the counter will be decremented by one each time; In addition, the await () method does not specify that only one thread can execute the method. If multiple threads execute the await () method at the same time, these threads will be in a waiting state and enjoy the same lock in shared mode. The following is an example of its use:

public class CountDownLatchExample {
  public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(5);
    Service service = new Service(latch);
    Runnable task = () -> service.exec();

    for (int i = 0; i < 5; i++) {
      Thread thread = new Thread(task);
      thread.start();
    }

    System.out.println("main thread await. ");
    latch.await();
    System.out.println("main thread finishes await. ");
  }
}

public class Service {
  private CountDownLatch latch;

  public Service(CountDownLatch latch) {
    this.latch = latch;
  }

  public void exec() {
    try {
      System.out.println(Thread.currentThread().getName() + " execute task. ");
      sleep(2);
      System.out.println(Thread.currentThread().getName() + " finished task. ");
    } finally {
      latch.countDown();
    }
  }

  private void sleep(int seconds) {
    try {
      TimeUnit.SECONDS.sleep(seconds);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

        

In the above example, a CountDownLatch object is declared first, and five threads are created by the main thread to execute tasks respectively. In each task, the current thread will sleep for 2 seconds. After starting the thread, the main thread called CountDownLatch Await () method. At this time, the main thread will wait here for the five created threads to complete their tasks before continuing to execute. The results are as follows:

Thread-0 execute task. 
Thread-1 execute task. 
Thread-2 execute task. 
Thread-3 execute task. 
Thread-4 execute task. 
main thread await. 
Thread-0 finished task. 
Thread-4 finished task. 
Thread-3 finished task. 
Thread-1 finished task. 
Thread-2 finished task. 
main thread finishes await. 

  

It can be seen from the output results that the main thread starts five threads first, and then the main thread enters the waiting state. When these five threads finish executing tasks, the main thread ends the waiting. It should be noted in the above code that try is used in the thread executing the task Finally structure, which can ensure that countdownlatch. When an exception occurs in the created thread The countdown () method will also be executed, which ensures that the main thread will not be waiting all the time.

2, Use CountDownLatch to solve problems at work

1) define a thread pool

public class ThreadUtils {
    
    private static ExecutorService executor;

    static {
        /**
         * Build a thread pool
         * Get the number of cores of the server CPU: runtime getRuntime(). availableProcessors()
         * Thread pool definition size: CPU * 2 + 1
         */
        executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2 + 1,
                Runtime.getRuntime().availableProcessors() * 2 + 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10000));
    }
    /**
     * Thread execution task in thread pool
     */
    public static void execute(Runnable task) {
        executor.execute(task);
    }

}

2) the thread pool is combined with CountDownLatch for task batch parallel processing

/**
* Simulate that the thread pool processes tasks in batches. The main thread needs to wait for the sub task thread to finish executing. After the results are summarized, the main thread continues to execute
*/
public void handleLogin(List<String> paramList) {
// Use the threads in the thread pool to process business logic in batches and process tasks in parallel to improve the response speed of the terminal
CountDownLatch latch = new CountDownLatch(paramList.size());
for (String param : paramList) {
ThreadUtils.execute(() -> {
try {
log.info("Business logic processing, parameters:{}", param);
// Normal processing of business logic
} catch (Exception e) {
log.error("There is an error in calling the downstream system, and the exception is handled logically......");
} finally {
// After the business logic processing is completed, the counter is decremented by one [the current thread has finished processing the task, and the thread is released into the thread pool, waiting to process the next task]
latch.countDown();
}
});
}
// The main thread needs to wait for the execution of the subtask thread. After the results are summarized, the main thread continues to execute
try {
latch.await();
} catch (Exception e) {
log.error("Wait timeout", e);
throw new RuntimeException("System processing timed out. Please try again later");
}
}

3, CountDownLatch usage scenario

Scenario 1: CountDownLatch is very suitable for splitting tasks so that they can be executed in parallel. For example, if a task is executed for 2s and its request for data can be divided into five parts, then the task can be divided into five subtasks, which are executed by five threads respectively, and then summarized by the main thread after execution. At this time, the total execution time will depend on the slowest task. On average, It also greatly reduces the total execution time.

Scenario 2: the place where CountDownLatch is used is when some external links are used to request data, such as pictures. There is a similar situation in my project, because the image service we use only provides the function of obtaining a single image, and the time of obtaining the image each time varies, which generally takes 1.5s~2s. When we need to obtain pictures in batch, for example, a list page needs to display a series of pictures. If we use a single thread to obtain them in sequence, the waiting time will be very long. At this time, we can use CountDownLatch to split the operation of obtaining pictures and obtain pictures in parallel, which will shorten the total acquisition time.

 

Tags: Java

Posted by jovankau on Sat, 14 May 2022 19:09:57 +0300