Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this article, we'll take a list of Runnable objects and check whether they are all done. As we know, Runnable is an interface whose instances can run as a Thread. We'll use wrapping objects such as CompletableFuture and ThreadPoolExecutor to run those threads.

2. Example Setup

Let's create a basic Runnable which will only log a message and then pause for one millisecond:

static Runnable RUNNABLE = () -> {
    try {
        System.out.println("launching runnable");
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
};

We'll now create a List of Runnable. In this example, we'll repeatedly add the same Runnable. One way to achieve this is by using an IntStream:

List<Runnable> runnables = IntStream.range(0, 5)
    .mapToObj(x -> RUNNABLE)
    .collect(Collectors.toList());

Let's now see how we can run those Runnable objects and get to know if they're all done.

3. Using CompletableFuture

Since Java 8, we can use the built-in CompletableFuture‘s isDone() method for this purpose.

CompletableFuture objects make asynchronous programming in Java easier. Given our Runnable list, we'll use the runAsync() method of CompletableFuture to run the associated tasks asynchronously. Let's note that by default, all those tasks will be run on the ForkJoinPool.

For further purposes, we'll want to wrap all the resultant CompletableFuture in an array:

CompletableFuture<?>[] completableFutures = runnables.stream()
    .map(CompletableFuture::runAsync)
    .toArray(CompletableFuture<?>[]::new);

Now, all our Runnable tasks are wrapped into CompletableFuture executions. This means that those tasks will run asynchronously in the background while our program continues.

To find out if all executions are completed at any point in our program, we'll create a new wrapping CompletableFuture from our array. The allOf() method will allow us to do so. Then, we'll apply the isDone() method directly to the wrapping CompletableFuture:

boolean isEveryRunnableDone = CompletableFuture.allOf(completableFutures)
    .isDone();

If any of the CompletableFuture is still running, isEveryRunnableDone will be false, otherwise, it will be true.

4. Using ThreadPoolExecutor

Since Java 5, thread pools have provided additional tools to help manage resources in a concurrent environment. In particular, they maintain some statistics such as the number of completed tasks they hold.

4.1. Count the Number of Remaining Tasks

Let's create a ThreadPoolExecutor with, for instance, five threads. We'll then submit each Runnable for execution by using the execute() method:

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
runnables.forEach(executor::execute);

Now we can count the number of running tasks in the ThreadPoolExecutor using the getActiveCount() method:

int numberOfActiveThreads = executor.getActiveCount();

The question here is, could we just compare this number to 0 to check if any Runnable is still running? Things are actually a little bit more complicated than that. The problem is that the number returned by the getActiveCount() method is an approximation, as stated by the class's documentation. Thus, we can't rely on it to make any decision.

4.2. Check if All Tasks Have Been Terminated

The getActiveCount() method doesn't return an exact value because it could be quite computationally intensive to do so. Thus, let's discard right away the option to implement our own counter.

On the other hand, the awaitTermination() method will let us know if all tasks are completed. But first, we'll need to call the shutdown() method on the executor. Calling this method will make sure that all the submitted tasks will complete. However, it prevents new tasks from being added to the executor:

executor.shutdown();

We've made sure that our ThreadPoolExecutor will correctly shut down. We can now check at any time if the pool has any running tasks by calling awaitTermination(). This method will block until the given timeout or until all tasks are done. For instance, let's use a one-second timeout for the sake of our example:

boolean isEveryRunnableDome = executor.awaitTermination(1000, TimeUnit.MILLISECONDS);

If all tasks complete within one second, the method immediately returns true. Otherwise, the program will be blocked for one second and then return false.

Last but not least, we should note that awaitTermination() throws an InterruptedException if any of the underlying threads got interrupted.

5. Conclusion

In this tutorial, we've seen how to check if all Runnables are done. With Java versions greater than 8, this is pretty straight-forward thanks to the CompletableFuture class. With older versions, we need to choose wisely our timeouts because the program will potentially be blocked for the duration we set.

As always, the code can be found over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
0 Comments
Inline Feedbacks
View all comments