How to use the Java CountDownLatch
source link: https://vladmihalcea.com/java-countdownlatch/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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:
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 onceendLatch
– 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.
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK