Contents

Running multiple threads and getting the first result

Written by: David Vlijmincx

Introduction

InvokeAny is a method to start multiple threads at once. The result of the first thread that finishes will be returned to the caller. In this article, we will explore the different invokeAny methods you can use in Java 19 and beyond. We will first cover two new ways that are using virtual threads. The last example will use platform threads.

InvokeAny with virtual threads

The first example we are going to look at uses virtual threads. We have a try-with-recourse statement that creates an executor that creates a virtual thread for each task. At line 9, the invokeAny method is called with a list of tasks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static Dog getFastestDogWithVirtualThreads() throws ExecutionException, InterruptedException {
    try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
        var dog = new Dog();

        var tasks = new ArrayList<Callable<Dog>>();
        tasks.add(() -> dog.getMax());
        tasks.add(() -> dog.getRex());

        return "result: " + executor.invokeAny(tasks);
    }
}

InvokeAny with structured concurrency

Structured concurrency is a new way of managing the lifetime of threads in Java. The lifetime is managed through a StructuredTaskScope. There are mainly two scopes, one with InvokeAny behavior and one with InvokeAll behavior. For this article, we are going to implement InvokeAny. To make a StructuredTaskScope that stops the threads when the first one finishes, we need to call the ShutdownOnSuccess method.

We need to call the fork method to add a task for the scope to execute. So, after starting all the tasks we wanted, two in this example. Then we call the join method on the scope, and this method will block till the first thread is finished. After the join is done we can get the result from the scope.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static Dog getFastestDogWithStructuredConcurrency() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnSuccess<Dog>()) {
        var dog = new Dog();
        scope.fork(() -> dog.getMax());
        scope.fork(() -> dog.getRex());
        scope.join();

        return "result: " + scope.result();
    }
}

InvokeAny with platform threads

Platform threads are the threads we know from previous Java versions. These threads are tightly coupled to threads that are managed by the operating system. The example below looks like the first example that uses virtual threads because we use an ExecutorService. This time the ExecutorService we are using is a CachedThreadPool. This pool creates a new thread when needed but will reuse threads that have already finished their task.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static Dog getFastestDogWithPlatformThreads() throws ExecutionException, InterruptedException {
    try (ExecutorService executor = Executors.newCachedThreadPool()) {
        var dog = new Dog();

        var tasks = new ArrayList<Callable<Dog>>();
        tasks.add(() -> dog.getMax());
        tasks.add(() -> dog.getRex());

        return "result: " + executor.invokeAny(tasks);
    }
}

Conclusion

This article looked at three ways of implementing the invokeAny method. We first used ExecutorService, which creates virtual threads for each task it needs to perform. After that, we saw how to make a StructuredTaskScope that behaves like an invokeAny method. The last example we saw used platform threads we are already familiar with from previous Java versions.

Further reading

More about virtual threads in Java: