With some applications that use several threads there are situations where one thread can only start after some others have completed.
For example, image a program that downloads a bunch of web pages, zips them and send then the zip file via email. If you program this in a multithreaded way, the thread that zips the downloaded web pages cannot start before the downloads are complete.
How do you do this? One very simple way is to use a CountDownLatch
from the java.util.concurrent package
( a package every Java developer should have a closer look at).
With a CountDownLatch
you can specify a number and then count down by 1 once an operation has completed. If all operations have completed and the count is 0, another thread that uses the same CountDownLatch
as a synchronisation tool using the await
method can do it’s work.
Let’s look at a simple example. The first class is a simple Runnabe
that does some work. In our example it does nothing really useful, it just sleeps for a random number of milliseconds to simulate some work.
import java.util.Random; import java.util.concurrent.CountDownLatch; public class Worker implements Runnable { private CountDownLatch countDownLatch; public Worker(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { Thread.sleep(getRandomSeconds()); // sleep random time to simulate long running task System.out.println(Counting down: + Thread.currentThread().getName()); this.countDownLatch.countDown(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } // returns a long between 0 and 9999 private long getRandomSeconds() { Random generator = new Random(); return Math.abs(generator.nextLong() % 10000); } }
The only really interesting line here is the call to:
this.countDownLatch.countDown();
Once the task is done, the counter in the CountDownLatch
is decremented by one.
Here is the 2nd class that uses this Runnable
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class WorkManager { private CountDownLatch countDownLatch; private static final int NUMBER_OF_TASKS = 5; public WorkManager() { countDownLatch = new CountDownLatch(NUMBER_OF_TASKS); } public void finishWork() { try { System.out.println(START WAITING); countDownLatch.await(); System.out.println(DONE WAITING); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } public void startWork() { ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_TASKS); for (int i = 0; i < NUMBER_OF_TASKS; i++) { Worker worker = new Worker(countDownLatch); executorService.execute(worker); } executorService.shutdown(); } public static void main(String[] args) { WorkManager workManager = new WorkManager(); System.out.println(START WORK); workManager.startWork(); System.out.println(WORK STARTED); workManager.finishWork(); System.out.println(FINISHED WORK); } }
The startWork
method uses and ExecutorService
(another useful and important class from java.util.concurrent
) to start the Runnables
.
In the method finishWork
we call the await
method that waits until the counter inside the CountDownLatch
is 0.
If you run this example you get the following output:
START WORK WORK STARTED START WAITING Counting down: pool-1-thread-3 Counting down: pool-1-thread-4 Counting down: pool-1-thread-1 Counting down: pool-1-thread-5 Counting down: pool-1-thread-2 DONE WAITING FINISHED WORK
As you can see, 5 different threads are started and the finishWork
method does not complete it’s work until the
CountDownLatch
is at 0.
As you can see, using a CountDownLatch
is very easy. There are other similar classes in the java.util.concurrent
like a CyclicBarrier
which is worth looking at. In upcoming posts, I will write more about the java.util.concurrent
package ant it’s useful classes, interfaces and methods.
Instead of the ExecutorService
you can just use java.lang.Thread
but I recommend always using the higher level ExecutorService
whenever possible. With all the stuff in java.util.concurrent
, there is rarely a need to use the low level classes like java.lang.Thread
(which does not mean you shouldn’t know how they work!).