Waiting for background tasks to finish using the CountDownLatch class in Java

Imagine the situation where you execute a number of long running methods. Also, let’s say that the very last time consuming process depends on the previous processes, let’s call them prerequisites. The dependence is “sequential” meaning that the final stage should only run if the prerequisites have completed and returned. The first implementation may very well be sequential where the long running methods are called one after the other and each of them blocks the main thread.

However, in case the prerequisites can be executed independently then there’s a much better solution: we can execute them in parallel instead. Independence in this case means that prerequisite A doesn’t need any return value from prerequisite B in which case parallel execution of A and B is not an option.

In this post we’ll examine this situation and see how to implement it in Java using the CountDownLatch class.

The service interface

We’ll put the long running processes behind an interface:

public interface MessagePrinterService
{
    public void print(String message);
}

The prerequisites will be represented by the following 4 implementations:

public class AnnoyedMessagePrinterService implements MessagePrinterService
{

    @Override
    public void print(String message)
    {
        try
        {
            Thread.sleep(5000);
        } catch (InterruptedException ex)
        {
            //ignore
        }
        
        System.out.println("What now??? ".concat(message));
    }
}

public class BlockCapitalsMessagePrinterService implements MessagePrinterService
{

    @Override
    public void print(String message)
    {
        try
        {
            Thread.sleep(4000);
        } catch (InterruptedException ex)
        {
            //ignore
        }
        System.out.println(message.toUpperCase());
    }
    
}

public class ReversedMessagePrinterService implements MessagePrinterService
{

    @Override
    public void print(String message)
    {
        try
        {
            Thread.sleep(3000);
        } catch (InterruptedException ex)
        {
            //ignore
        }       
        
        System.out.println(new StringBuilder(message).reverse().toString());
    }
    
}

public class ScrambledMessagePrinterService implements MessagePrinterService
{

    @Override
    public void print(String message)
    {
        try
        {
            Thread.sleep(2000);
        } catch (InterruptedException ex)
        {
            //ignore
        }

        ArrayList<Character> chars = new ArrayList<>(message.length());
        for (char c : message.toCharArray())
        {
            chars.add(c);
        }
        Collections.shuffle(chars);
        char[] shuffled = new char[chars.size()];
        for (int i = 0; i < shuffled.length; i++)
        {
            shuffled[i] = chars.get(i);
        }
        System.out.println(new String(shuffled));
    }

}

We also have a fifth implementation that will simply print the supplied message without any changes. This implementation will be the final one to be called in our demo code later on:

public class UnchangedMessagePrinterService implements MessagePrinterService
{
    @Override
    public void print(String message)
    {
        try
        {
            Thread.sleep(1000);
        } catch (InterruptedException ex)
        {
            //ignore
        }
        
        System.out.println(message);
    }   
}

The sequential solution

Here we simply call each printer service to print the message one after the other with the UnchangedMessagePrinterService coming last:

private void tryCountDownLatchToWaitForTasks()
{

    String message = "My latest invention is going to save the world!";
    MessagePrinterService annoyed = new AnnoyedMessagePrinterService();
    MessagePrinterService blockCapitals = new BlockCapitalsMessagePrinterService();
    MessagePrinterService reversed = new ReversedMessagePrinterService();
    MessagePrinterService scrambled = new ScrambledMessagePrinterService();
    MessagePrinterService unchanged = new UnchangedMessagePrinterService();

    Instant start = Instant.now();
    annoyed.print(message);
    blockCapitals.print(message);
    reversed.print(message);
    scrambled.print(message);
    unchanged.print(message);
    Instant finish = Instant.now();
    Duration duration = Duration.between(start, finish);
    long seconds = duration.getSeconds();
    System.out.println(seconds);
}

You’ll see messages similar to the following printed to the standard out:

What now??? My latest invention is going to save the world!
MY LATEST INVENTION IS GOING TO SAVE THE WORLD!
!dlrow eht evas ot gniog si noitnevni tsetal yM
arttids ysslei M tiogeon !w en ntlthvneoigvao
My latest invention is going to save the world!

As expected the total execution time will be around 15 seconds. It is the sum of all Thread.sleep wait times in the 5 message printers.

The parallellised solution

We can immediately see that the prerequisite message printers can be called in parallel. There’s nothing in e.g. ReversedMessagePrinterService that depends on AnnoyedMessagePrinterService.

The CountDownLatch is sort of a helper class that makes method synchronisation easier. Its constructor accepts an integer which is a counter. The counter indicates the number of times the latch must be called before its await method can pass. The latch is called through its countDown method. If the latch counter has reached 0 then its await method will return true. We can use that boolean condition to continue with the final method execution.

In our example the latch counter will be 4 since we have 4 prerequisites. They can run in parallel. Every time a message printer is executed we decrease the latch counter by calling its countDown method.
The expected total execution time will be reduced to 6 seconds: 5 seconds for the AnnoyedMessagePrinterService during which time the other 3 prerequisites can return. Then we have 1 more second for the final UnchangedMessagePrinterService class to finish its message printing task.

The CountDownLatch class cannot be used on its own to initiate parallel threads, it is only a helper class similar to a lock. You’ll still need to be aware of the ExecutorService class which we discussed before on this blog. If you don’t know what it is and how to use it then start here and here.

Here’s a code example on how to use CountDownLatch. Note how Java 8 enables us to submit anonymous Runnable blocks to the ExecutorService.submit method using a lambda expression:

private void tryCountDownLatchToWaitForTasks()
{
    CountDownLatch messagePrinterCountDownLatch = new CountDownLatch(4);
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    String message = "My latest invention is going to save the world!";
    MessagePrinterService annoyed = new AnnoyedMessagePrinterService();
    MessagePrinterService blockCapitals = new BlockCapitalsMessagePrinterService();
    MessagePrinterService reversed = new ReversedMessagePrinterService();
    MessagePrinterService scrambled = new ScrambledMessagePrinterService();
    MessagePrinterService unchanged = new UnchangedMessagePrinterService();

    Instant start = Instant.now();
    cachedThreadPool.submit(()
            -> 
            {
                annoyed.print(message);
                messagePrinterCountDownLatch.countDown();
    });

    cachedThreadPool.submit(()
            -> 
            {
                blockCapitals.print(message);
                messagePrinterCountDownLatch.countDown();
    });

    cachedThreadPool.submit(()
            -> 
            {
                reversed.print(message);
                messagePrinterCountDownLatch.countDown();
    });

    cachedThreadPool.submit(()
            -> 
            {
                scrambled.print(message);
                messagePrinterCountDownLatch.countDown();
    });

    cachedThreadPool.submit(()
            -> 
            {
                try
                {
                    if (messagePrinterCountDownLatch.await(10, TimeUnit.SECONDS))
                    {
                        unchanged.print(message);
                        Instant finish = Instant.now();
                        Duration duration = Duration.between(start, finish);
                        long seconds = duration.getSeconds();
                        System.out.println(seconds);
                    } else
                    {
                        System.out.println("The final message printer could not execute properly.");
                    }
                } catch (InterruptedException ie)
                {
                    System.out.println("The count down latch await process has been interrupted.");
                    Thread.currentThread().interrupt();
                }
    });
}

Here’s a typical output:

dn tgistovo ss enarlweeMnhateto tlio v! giyn i
!dlrow eht evas ot gniog si noitnevni tsetal yM
MY LATEST INVENTION IS GOING TO SAVE THE WORLD!
What now??? My latest invention is going to save the world!
My latest invention is going to save the world!

The total execution time was 6 seconds.

Note how we can supply a timeout to the await method. Await will return false if the latch counter has not reached 0 before the assigned wait time.

In case we supply a timeout that we know is too short, like here…:

if (messagePrinterCountDownLatch.await(3, TimeUnit.SECONDS))

…then the program output will be different:

iiri nyo vwdi !aoteenaloosstth tts lMennv egg
!dlrow eht evas ot gniog si noitnevni tsetal yM
The final message printer could not execute properly.
MY LATEST INVENTION IS GOING TO SAVE THE WORLD!
What now??? My latest invention is going to save the world!

The 2 fastest message printers were executed before the await timeout was reached and we got to the “The final message printer could not execute properly.” bit. Note also that the other 2 prerequisites were not interrupted or anything, but the latch simply didn’t wait for them and returned false.

View all posts related to Java here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: