How to use the Java CountDownLatch

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

In this article, we are going to see how to use the Java CountDownLatch to write test cases that take concurrency into consideration.

The Java CountDownLatch has been available since version 1.5, and it’s part of the java.util.concurrent package that contains many other threading-related utilities.

The Java CountDownLatch class

The Java CountDownLatch class provides the following methods:

Java CountDownLatch class

The most important methods are the await and countDown methods.

The await method is used to pause the execution of the current thread until the counter of the CountDownLatch reaches the value of 0 or the thread is interrupted.

The countDown method is used to decrease the value of the internal counter.

Therefore, the goal of the Java CountDownLatch class is to control the moment when one or more threads, that are waiting on the CountDownLatch are going to resume their execution.

Coordinating Java Threads

Let’s say we have the following example in which we start 5 worker threads from the main thread of execution:

@Test
public void testNoCoordination() {
    LOGGER.info("Main thread starts");
⠀
    int workerThreadCount = 5;
⠀
    for (int i = 1; i <= workerThreadCount; i++) {
        String threadId = String.valueOf(i);
        new Thread(
            () -> LOGGER.info(
                "Worker thread {} runs",
                threadId
            ),
            "Thread-" + threadId
        ).start();
    }
⠀
    LOGGER.info("Main thread finishes");
}

When running the above test case, we get the following output:

[main]: Main thread starts
[main]: Main thread finishes
[Thread-4]: Worker thread 4 runs
[Thread-1]: Worker thread 1 runs
[Thread-5]: Worker thread 5 runs
[Thread-3]: Worker thread 3 runs
[Thread-2]: Worker thread 2 runs

The main thread starts and finishes before the worker threads get to execute, and this can be a problem if we want to run some assertions that validate the outcome of the worker thread executions.

Therefore, we need to coordinate the main thread and the worker threads so that the main thread waits for the worker threads to finish before finishing its own execution.

For this purpose, we are going to use a CountDownLatch to coordinate the main and the worker threads:

@Test
public void testCountDownLatch() throws InterruptedException {
    LOGGER.info("Main thread starts");
⠀
    int workerThreadCount = 5;
⠀
    CountDownLatch endLatch = new CountDownLatch(workerThreadCount);
⠀
    for (int i = 1; i <= workerThreadCount; i++) {
        String threadId = String.valueOf(i);
        new Thread(
            () -> {
                LOGGER.info(
                    "Worker thread {} runs", 
                    threadId
                );
⠀
                endLatch.countDown();
            },
            "Thread-" + threadId
        ).start();
    }
⠀
    LOGGER.info("Main thread waits for the worker threads to finish");
⠀
    endLatch.await();
⠀
    LOGGER.info("Main thread finishes");
}

The endLatch is created with a counter value matching the number of worker threads.

The main thread will wait on the endLatch, and its execution will be resumed only when the counter value teaches the value of 0.

Because each worker thread decreases the counter value using the countDown method call, after all worker threads have finished, the counter will reach the value of 0, and the main thread will resume its execution.

When executing the testCountDownLatch test case, we can see that the CountDownLatch works as expected:

[main]: Main thread starts
[main]: Main thread waits for the worker threads to finish
[Thread-1]: Worker thread 1 runs
[Thread-3]: Worker thread 3 runs
[Thread-2]: Worker thread 2 runs
[Thread-5]: Worker thread 5 runs
[Thread-4]: Worker thread 4 runs
[main]: Main thread finishes

This time, the main Thread waits for the worker threads to finish before ending its own execution.

A real-life example

To see where you could use the Java CountDownLatch, consider the integration test from my previous article about Race Conditions:

@Test
public void testParallelExecution() {
    assertEquals(10L, getAccountBalance("Alice-123"));
    assertEquals(0L, getAccountBalance("Bob-456"));
⠀
    int threadCount = threadCount();
⠀
    CountDownLatch startLatch = new CountDownLatch(1);
    CountDownLatch endLatch = new CountDownLatch(threadCount);
⠀
    for (int i = 0; i < threadCount; i++) {
        new Thread(() -> {
            awaitOnLatch(startLatch);
⠀
            transfer("Alice-123", "Bob-456", 5L);
⠀
            endLatch.countDown();
        }).start();
    }
⠀
    LOGGER.info("Starting threads");
    startLatch.countDown();
    awaitOnLatch(endLatch);
⠀
    LOGGER.info("Alice's balance: {}", getAccountBalance("Alice-123"));
    LOGGER.info("Bob's balance: {}", getAccountBalance("Bob-456"));
}

This time, we are using two CountDownLatch objects:

  • startLatch – to start the worker threads at once
  • endLatch – to notify the main thread that the worker threads are done processing

When building the startLatch Object, we provide the counter value of 1 to the CountDownLatch constructor because it’s just the main thread that’s going to count down its value after the worker threads are created.

And the first thing that a worker thread does after starting running is to wait on the startLatch. This way, the worker threads will proceed with their tasks at once, therefore increasing the likelihood of contention, which we are aiming for in our race condition test case.

The endLatch uses a counter value that matches the number of worker threads we are creating because each of those threads is going to count down that value after finishing their processing task.

After the worker threads are created and started, the main thread will count down the startLatch, causing all worker threads to resume their execution.

After starting the worker threads , the main thread pauses its execution and starts waiting on the endLatch for all worker threads to finish.

After all worker threads have finished, the endLatch counter reaches the value of 0, the main thread resumes its execution and prints the account balances.

That’s why the CountDownLatch is very useful, as it allows the main thread to print the account balances after all the worker threads have finished processing.

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

The Java CountDownLatch can be very useful when we have to coordinate thread executions in order to assert the outcome of their execution.

So, if you want to test how your data access logic works in a concurrent environment, the CountDownLatch object can help you achieve your goal.

Transactions and Concurrency Control eBook

One Comment on “How to use the Java CountDownLatch

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.