A thread pool is a collection of threads that are kept ready to execute tasks concurrently as they are assigned by the program.
By maintaining a pool of threads, performance is improved and execution latency is reduced, since it avoids the overhead of repeatedly creating and destroying threads for short-lived tasks.
Java provides the Executor Framework, which is built around the Executor interface, the ExecutorService sub-interface, and the ThreadPoolExecutor class that implements both.
The Executor Framework is an implementation of the Producer-Consumer pattern.
By using the executor framework, we only write our concurrent code in the form of parallel tasks and submit them to an instance of executor for execution.

What You Need
- About 35 minutes
- A favorite text editor or IDE
- Java 8 or later
1. Executor
The java.util.concurrent.Executor interface describes the functionalities allowing deferred execution of tasks implemented as Runnable.
It defines only one method :
- void execute(Runnable command) : Run the task provided as a parameter possibly in the future.
Depending on the implementation, the task may be executed in a dedicated thread or in the current thread.
It has two child interfaces :
- ExecutorService : it defines the functionalities of a service allowing the execution of Runnable or Callable tasks;
- ScheduledExecutorService which inherits from the ExecutorService interface : it defines the functionalities of a service for the execution of scheduled or repeated tasks.
2. ExecutorService
The ExecutorService interface inherits from the Executor interface.
ExecutorService automatically provides a pool of threads and an API for assigning tasks to it.
2.1 Creating ExecutorService
The easiest way to create ExecutorService is to use one of the factory methods of the Executors class :
- newFixedThreadPool : Creates a thread pool with a fixed number of threads that are reused to process tasks from a shared, unbounded queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-1 : executing task 0
pool-1-thread-1 : executing task 3
pool-1-thread-2 : executing task 1
pool-1-thread-2 : executing task 5
pool-1-thread-3 : executing task 2
pool-1-thread-3 : executing task 7
pool-1-thread-3 : executing task 8
pool-1-thread-3 : executing task 9
pool-1-thread-1 : executing task 4
pool-1-thread-2 : executing task 6
- newCachedThreadPool : Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-3 : executing task 2
pool-1-thread-1 : executing task 0
pool-1-thread-6 : executing task 5
pool-1-thread-9 : executing task 8
pool-1-thread-7 : executing task 6
pool-1-thread-8 : executing task 7
pool-1-thread-4 : executing task 3
pool-1-thread-5 : executing task 4
pool-1-thread-2 : executing task 1
pool-1-thread-10 : executing task 9
- newSingleThreadExecutor : Creates a thread pool with a single worker thread that processes tasks from an unbounded queue.;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService es = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
}
}
}
Above code snippet has below output :
pool-1-thread-1 : executing task 0
pool-1-thread-1 : executing task 1
pool-1-thread-1 : executing task 2
pool-1-thread-1 : executing task 3
pool-1-thread-1 : executing task 4
pool-1-thread-1 : executing task 5
pool-1-thread-1 : executing task 6
pool-1-thread-1 : executing task 7
pool-1-thread-1 : executing task 8
pool-1-thread-1 : executing task 9
- newWorkStealingPool : Creates a work-stealing thread pool using the number of available processors as its target parallelism level.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WorkStealingPool {
public static void main(String[] args) {
ExecutorService es = Executors.newWorkStealingPool();
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
}
while (!es.isTerminated()) {
// waiting for all tasks to be handled by es
}
}
}
Below is the output of above code snippet :
ForkJoinPool-1-worker-4 : executing task 3
ForkJoinPool-1-worker-3 : executing task 2
ForkJoinPool-1-worker-3 : executing task 7
ForkJoinPool-1-worker-3 : executing task 8
ForkJoinPool-1-worker-3 : executing task 9
ForkJoinPool-1-worker-5 : executing task 4
ForkJoinPool-1-worker-1 : executing task 0
ForkJoinPool-1-worker-2 : executing task 1
ForkJoinPool-1-worker-6 : executing task 5
ForkJoinPool-1-worker-4 : executing task 6
Another way is to create directly an ExecutorService, because it is an interface, an instance of any its implementations can be used.
The most common implementation is ThreadPoolExecutor, it is an extensible thread pool implementation with lots of parameters and hooks for fine-tuning.
- corePoolSize : the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set;
- maximumPoolSize : the maximum number of threads to allow in the pool;
- keepAliveTime : when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating;
- timeUnit : the time unit for the keepAliveTime argument;
- workQueue : the queue to use for holding tasks before they are executed;
- threadFactory : the factory to use when the executor creates a new thread;
- rejectedExecutionHandler : the handler to use when execution is blocked because the thread bounds and queue capacities are reached.
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class NewThreadPoolExecutor {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 3;
long keepAliveTime = 10l;
TimeUnit unit = TimeUnit.SECONDS;
int queueSize = 5;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue(queueSize);
ThreadFactory threadFactory = new ThreadFactory() {
int threadNumber = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "MyThread-" + (threadNumber++));
}
};
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
ExecutorService es = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, handler);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
main : executing task 8
main : executing task 9
MyThread-2 : executing task 7
MyThread-2 : executing task 2
MyThread-2 : executing task 3
MyThread-2 : executing task 4
MyThread-2 : executing task 5
MyThread-2 : executing task 6
MyThread-1 : executing task 1
MyThread-0 : executing task 0
2.2 Assigning Tasks to ExecutorService
ExecutorService can execute Runnable and Callable tasks.
To assign tasks to the ExecutorService, we can use several methods :
- execute() : it is inherited from the Executor interface, it executes a Runnable task and doesn’t give any possibility to get the result of a task’s execution;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
}
}
}
The output of above code snippet is below :
pool-1-thread-1 : executing task 0
pool-1-thread-3 : executing task 2
pool-1-thread-2 : executing task 1
pool-1-thread-2 : executing task 5
pool-1-thread-1 : executing task 3
pool-1-thread-1 : executing task 7
pool-1-thread-1 : executing task 8
pool-1-thread-3 : executing task 4
pool-1-thread-2 : executing task 6
pool-1-thread-1 : executing task 9
- submit() : submits a Runnable task and returns a result of type Future (null) which can be used to check when the Runnable has completed;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AssignTaskBySubmitRunnable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
try {
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
futures.add(es.submit(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber)));
}
for (int i = 0; i < futures.size(); i++) {
System.out.println(futures.get(i).get());
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-1 : executing task 0
pool-1-thread-3 : executing task 2
pool-1-thread-3 : executing task 3
pool-1-thread-3 : executing task 5
pool-1-thread-3 : executing task 6
pool-1-thread-1 : executing task 4
pool-1-thread-1 : executing task 8
pool-1-thread-3 : executing task 7
null
pool-1-thread-2 : executing task 1
pool-1-thread-1 : executing task 9
null
null
null
null
null
null
null
null
null
- submit() : submits a Callable task and returns a result of type Future;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AssignTaskBySubmitCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
try {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
futures.add(es.submit(
() -> Thread.currentThread().getName() + " : executing task " + taskNumber));
}
for (int i = 0; i < futures.size(); i++) {
System.out.println(futures.get(i).get());
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-1 : executing task 0
pool-1-thread-2 : executing task 1
pool-1-thread-3 : executing task 2
pool-1-thread-2 : executing task 3
pool-1-thread-3 : executing task 4
pool-1-thread-3 : executing task 5
pool-1-thread-2 : executing task 6
pool-1-thread-1 : executing task 7
pool-1-thread-3 : executing task 8
pool-1-thread-2 : executing task 9
- invokeAny() : assigns a collection of tasks, returns the result of a successful execution of one task;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AssignTaskByInvokeAny {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = new ArrayList<>();
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
tasks.add(() -> {
String result = Thread.currentThread().getName() + " : executing task " + taskNumber;
System.out.println(result);
return "The returned result by invokeAny is : " + result;
});
}
String executed = es.invokeAny(tasks);
System.out.println(executed);
} finally {
es.shutdown();
}
}
}
Above code snippet has below output :
pool-1-thread-1 : executing task 0
pool-1-thread-3 : executing task 2
pool-1-thread-2 : executing task 1
pool-1-thread-3 : executing task 3
pool-1-thread-2 : executing task 4
The returned result by invokeAny is : pool-1-thread-3 : executing task 2
- invokeAll() : assigns a collection of tasks, causing each to run, and returns the result of all task executions in the form of a list of objects of Future type.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AssignTaskByInvokeAll {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = new ArrayList<>();
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
tasks.add(() -> Thread.currentThread().getName() + " : executing task " + taskNumber);
}
List<Future<String>> futures = es.invokeAll(tasks);
for (Future<String> future : futures) {
System.out.println(future.get());
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-1 : executing task 0
pool-1-thread-2 : executing task 1
pool-1-thread-3 : executing task 2
pool-1-thread-3 : executing task 3
pool-1-thread-2 : executing task 4
pool-1-thread-3 : executing task 5
pool-1-thread-2 : executing task 6
pool-1-thread-3 : executing task 7
pool-1-thread-2 : executing task 8
pool-1-thread-3 : executing task 9
2.3 Shutting Down an ExecutorService
The ExecutorService will not be automatically destroyed when there is no task to process.
It will stay alive and wait for new work to do.
In some cases this is very helpful, such as when an app needs to process tasks that appear on an irregular basis or the task quantity is not known at compile time.
On the other hand, an app could reach its end but not be stopped because a waiting ExecutorService will cause the JVM to keep running.
To properly shut down an ExecutorService, we can use :
- shutdown() : it doesn’t cause immediate destruction of the ExecutorService.
It will make the ExecutorService stop accepting new tasks and shut down after all running threads finish their current work; - shutdownNow() : it tries to destroy the ExecutorService immediately, but it doesn’t guarantee that all the running threads will be stopped at the same time and it returns a list of tasks that are waiting to be processed.
It is up to the developer to decide what to do with these tasks.
One best practice to shutdown the ExecutorService is to use both of shutdown and shutdownNow methods combined with the awaitTermination() method.
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ShutingDownES {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.execute(
() -> System.out.println(
Thread.currentThread().getName() + " : executing task " + taskNumber));
}
} finally {
es.shutdown();
try {
if (!es.awaitTermination(10, TimeUnit.MILLISECONDS)) {
List<Runnable> notFinishedTasks = es.shutdownNow();
executeByMain(notFinishedTasks);
}
} catch (InterruptedException e) {
List<Runnable> notFinishedTasks = es.shutdownNow();
executeByMain(notFinishedTasks);
}
}
}
private static void executeByMain(List<Runnable> notFinishedTasks) {
notFinishedTasks.forEach(task -> {
task.run();
});
}
}
Below is the output of above code snippet :
pool-1-thread-3 : executing task 2
pool-1-thread-2 : executing task 1
main : executing task 3
main : executing task 4
main : executing task 5
main : executing task 6
main : executing task 7
main : executing task 8
main : executing task 9
pool-1-thread-1 : executing task 0
With this approach, the ExecutorService will first stop taking new tasks and then wait up to a specified period of time for all tasks to be completed.
If that time expires, the execution is stopped immediately.
3. ScheduledExecutorService
The ScheduledExecutorService uses a pool of threads to run tasks after some predefined delay and/or periodically.
The execution of such a task is done asynchronously in a dedicated thread.
Since the ScheduledExecutorService interface inherits from the ExecutorService interface.
So it is also possible to use the execute() method inherited from the Executor interface or the submit() method inherited from the ExecutorService interface to request the immediate execution of a task.
3.1 Creating ScheduledExecutorService
Like ExecutorService, the easiest way to create ScheduledExecutorService is to use one of the factory methods of the Executors class :
- newScheduledThreadPool : Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically;
import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.schedule(() -> System.out.println(
Thread.currentThread().getName()
+ " : executing task "
+ taskNumber
+ " at : "
+ new Timestamp(System.currentTimeMillis())),
3, TimeUnit.SECONDS);
}
System.out.println(
"All the tasks have been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
All the tasks have been assigned to pool at : 2025-08-20 11:50:53.307
pool-1-thread-3 : executing task 2 at : 2025-08-20 11:50:56.295
pool-1-thread-1 : executing task 0 at : 2025-08-20 11:50:56.294
pool-1-thread-2 : executing task 1 at : 2025-08-20 11:50:56.294
pool-1-thread-3 : executing task 3 at : 2025-08-20 11:50:56.322
pool-1-thread-1 : executing task 4 at : 2025-08-20 11:50:56.322
pool-1-thread-1 : executing task 7 at : 2025-08-20 11:50:56.322
pool-1-thread-1 : executing task 8 at : 2025-08-20 11:50:56.322
pool-1-thread-1 : executing task 9 at : 2025-08-20 11:50:56.322
pool-1-thread-2 : executing task 5 at : 2025-08-20 11:50:56.322
pool-1-thread-3 : executing task 6 at : 2025-08-20 11:50:56.322
- newSingleThreadScheduledExecutor : Creates a single-threaded executor that can schedule commands to run after a given delay, or to execute periodically;
import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class SingleThreadScheduledExecutor {
public static void main(String[] args) {
ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor();
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.schedule(() -> System.out.println(
Thread.currentThread().getName()
+ " : executing task "
+ taskNumber
+ " at : "
+ new Timestamp(System.currentTimeMillis())),
3, TimeUnit.SECONDS);
}
System.out.println(
"All the tasks have been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
All the tasks have been assigned to pool at : 2025-08-20 11:53:44.872
pool-1-thread-1 : executing task 0 at : 2025-08-20 11:53:47.866
pool-1-thread-1 : executing task 1 at : 2025-08-20 11:53:47.893
pool-1-thread-1 : executing task 2 at : 2025-08-20 11:53:47.893
pool-1-thread-1 : executing task 3 at : 2025-08-20 11:53:47.893
pool-1-thread-1 : executing task 4 at : 2025-08-20 11:53:47.894
pool-1-thread-1 : executing task 5 at : 2025-08-20 11:53:47.894
pool-1-thread-1 : executing task 6 at : 2025-08-20 11:53:47.894
pool-1-thread-1 : executing task 7 at : 2025-08-20 11:53:47.894
pool-1-thread-1 : executing task 8 at : 2025-08-20 11:53:47.894
pool-1-thread-1 : executing task 9 at : 2025-08-20 11:53:47.894
Another way is to create directly an ScheduledExecutorService, because it is an interface, an instance of any its implementations can be used.
The most common implementation is ScheduledThreadPoolExecutor, it is an extensible thread pool implementation with lots of parameters and hooks for fine-tuning.
- corePoolSize : the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set;
- threadFactory : the factory to use when the executor creates a new thread;
- rejectedExecutionHandler : the handler to use when execution is blocked because the thread bounds and queue capacities are reached.
import java.sql.Timestamp;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
public class NewScheduledThreadPoolExecutor {
public static void main(String[] args) {
int corePoolSize = 2;
ThreadFactory threadFactory = new ThreadFactory() {
int threadNumber = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "MyThread-" + (threadNumber++));
}
};
RejectedExecutionHandler handler = new ScheduledThreadPoolExecutor.CallerRunsPolicy();
ScheduledExecutorService es = new ScheduledThreadPoolExecutor(corePoolSize, threadFactory, handler);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.schedule(() -> System.out.println(
Thread.currentThread().getName()
+ " : executing task "
+ taskNumber
+ " at : "
+ new Timestamp(System.currentTimeMillis())),
3, TimeUnit.SECONDS);
}
System.out.println(
"All the tasks have been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
All the tasks have been assigned to pool at : 2025-08-21 16:17:37.988
MyThread-0 : executing task 0 at : 2025-08-21 16:17:40.954
MyThread-1 : executing task 1 at : 2025-08-21 16:17:40.977
MyThread-1 : executing task 3 at : 2025-08-21 16:17:40.982
MyThread-0 : executing task 2 at : 2025-08-21 16:17:40.982
MyThread-1 : executing task 4 at : 2025-08-21 16:17:40.983
MyThread-0 : executing task 5 at : 2025-08-21 16:17:40.983
MyThread-1 : executing task 6 at : 2025-08-21 16:17:40.983
MyThread-0 : executing task 7 at : 2025-08-21 16:17:40.983
MyThread-1 : executing task 8 at : 2025-08-21 16:17:40.983
MyThread-0 : executing task 9 at : 2025-08-21 16:17:40.983
3.2 Assigning Tasks to ScheduledExecutorService
ScheduledExecutorService can execute and schedule Runnable and Callable tasks.
To assign tasks to the ScheduledExecutorService, we can use several methods :
- schedule(Runnable command, long delay, TimeUnit unit) : Creates and executes a one-shot action that becomes enabled after the given delay;
import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
try {
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
es.schedule(() -> System.out.println(
Thread.currentThread().getName()
+ " : executing task "
+ taskNumber
+ " at : "
+ new Timestamp(System.currentTimeMillis())),
3, TimeUnit.SECONDS);
}
System.out.println(
"All the tasks have been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
All the tasks have been assigned to pool at : 2025-08-22 09:10:54.571
pool-1-thread-3 : executing task 2 at : 2025-08-22 09:10:57.557
pool-1-thread-2 : executing task 1 at : 2025-08-22 09:10:57.557
pool-1-thread-1 : executing task 0 at : 2025-08-22 09:10:57.557
pool-1-thread-3 : executing task 3 at : 2025-08-22 09:10:57.583
pool-1-thread-1 : executing task 5 at : 2025-08-22 09:10:57.583
pool-1-thread-2 : executing task 4 at : 2025-08-22 09:10:57.583
pool-1-thread-1 : executing task 7 at : 2025-08-22 09:10:57.584
pool-1-thread-2 : executing task 8 at : 2025-08-22 09:10:57.584
pool-1-thread-1 : executing task 9 at : 2025-08-22 09:10:57.584
pool-1-thread-3 : executing task 6 at : 2025-08-22 09:10:57.583
- schedule(Callable<V> callable, long delay, TimeUnit unit) : Creates and executes a ScheduledFuture that becomes enabled after the given delay;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class AssignTaskByScheduleCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
try {
List<ScheduledFuture<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
futures.add(es.schedule(
() -> Thread.currentThread().getName() + " : executing task " + taskNumber + " at : "
+ new Timestamp(System.currentTimeMillis()),
3,
TimeUnit.SECONDS));
}
System.out.println(
"All the tasks have been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
for (int i = 0; i < futures.size(); i++) {
System.out.println("Remaining delay for task " + i + " = " + futures.get(i).getDelay(TimeUnit.SECONDS));
}
for (int i = 0; i < futures.size(); i++) {
System.out.println(futures.get(i).get());
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
All the tasks have been assigned to pool at : 2025-08-22 09:13:35.78
Remaining delay for task 0 = 2
Remaining delay for task 1 = 2
Remaining delay for task 2 = 2
Remaining delay for task 3 = 2
Remaining delay for task 4 = 2
Remaining delay for task 5 = 2
Remaining delay for task 6 = 2
Remaining delay for task 7 = 2
Remaining delay for task 8 = 2
Remaining delay for task 9 = 2
pool-1-thread-1 : executing task 0 at : 2025-08-22 09:13:38.77
pool-1-thread-2 : executing task 1 at : 2025-08-22 09:13:38.77
pool-1-thread-3 : executing task 2 at : 2025-08-22 09:13:38.77
pool-1-thread-3 : executing task 3 at : 2025-08-22 09:13:38.787
pool-1-thread-2 : executing task 4 at : 2025-08-22 09:13:38.787
pool-1-thread-1 : executing task 5 at : 2025-08-22 09:13:38.787
pool-1-thread-3 : executing task 6 at : 2025-08-22 09:13:38.787
pool-1-thread-1 : executing task 7 at : 2025-08-22 09:13:38.787
pool-1-thread-1 : executing task 8 at : 2025-08-22 09:13:38.787
pool-1-thread-2 : executing task 9 at : 2025-08-22 09:13:38.787
- scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) : Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period, it means that the executions will commence after initialDelay then initialDelay + period, then initialDelay + 2 * period, and so on;
import java.sql.Timestamp;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AssignTaskByScheduleAtFixedRate {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
try {
AtomicInteger counter = new AtomicInteger();
ScheduledFuture<?> future = es.scheduleAtFixedRate(
() -> {
int current = counter.incrementAndGet();
System.out.println(Thread.currentThread().getName() +
" : increases counter by 1 at : "
+ new Timestamp(System.currentTimeMillis())
+ " , current counter value = " + current);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},
3,
2,
TimeUnit.SECONDS);
System.out.println("Task has been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
while (true) {
int current = counter.get();
if (current >= 5) {
System.out.println("Count >= 5, cancel the scheduledFuture!");
future.cancel(false);
break;
} else {
Thread.sleep(1000);
}
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
Task has been assigned to pool at : 2025-08-23 16:35:16.94
pool-1-thread-1 : increases counter by 1 at : 2025-08-23 16:35:19.93 , current counter value = 1
pool-1-thread-1 : increases counter by 1 at : 2025-08-23 16:35:21.93 , current counter value = 2
pool-1-thread-2 : increases counter by 1 at : 2025-08-23 16:35:23.93 , current counter value = 3
pool-1-thread-2 : increases counter by 1 at : 2025-08-23 16:35:25.93 , current counter value = 4
pool-1-thread-2 : increases counter by 1 at : 2025-08-23 16:35:27.93 , current counter value = 5
Count >= 5, cancel the scheduledFuture!
- scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) : Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next.
import java.sql.Timestamp;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AssignTaskByScheduleWithFixedDelay {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
try {
AtomicInteger counter = new AtomicInteger();
ScheduledFuture<?> future = es.scheduleWithFixedDelay(
() -> {
int current = counter.incrementAndGet();
System.out.println(Thread.currentThread().getName() +
" : increases counter by 1 at : "
+ new Timestamp(System.currentTimeMillis())
+ " , current counter value = " + current);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},
3,
2,
TimeUnit.SECONDS);
System.out.println("Task has been assigned to pool at : " + new Timestamp(System.currentTimeMillis()));
while (true) {
int current = counter.get();
if (current >= 5) {
System.out.println("Count >= 5, cancel the scheduledFuture!");
future.cancel(false);
break;
} else {
Thread.sleep(1000);
}
}
} finally {
es.shutdown();
}
}
}
The output of above code snippet is below :
Task has been assigned to pool at : 2025-08-23 16:38:54.968
pool-1-thread-1 : increases counter by 1 at : 2025-08-23 16:38:57.96 , current counter value = 1
pool-1-thread-1 : increases counter by 1 at : 2025-08-23 16:39:00.995 , current counter value = 2
pool-1-thread-2 : increases counter by 1 at : 2025-08-23 16:39:03.996 , current counter value = 3
pool-1-thread-2 : increases counter by 1 at : 2025-08-23 16:39:06.997 , current counter value = 4
pool-1-thread-2 : increases counter by 1 at : 2025-08-23 16:39:09.998 , current counter value = 5
Count >= 5, cancel the scheduledFuture!
To understand the difference between scheduleAtFixedRate and scheduleWithFixedDelay, suppose that there is a job of making coffee which takes 10 minutes.
If i schedule with a fixed rate of one hour, I’d have:
00:00: Start making coffee
00:10: Finish making coffee
01:00: Start making coffee
01:10: Finish making coffee
02:00: Start making coffee
02:10: Finish making coffee
If i schedule with a fixed delay of one hour, I’d have:
00:00: Start making coffee
00:10: Finish making coffee
01:10: Start making coffee
01:20: Finish making coffee
02:20: Start making coffee
02:30: Finish making coffee
4. ForkJoinPool
ForkJoinPool helps speed up parallel processing by attempting to use all available processor cores.
It accomplishes this through a divide and conquer approach.
- It first “forks” recursively breaking the task into smaller independent subtasks until they are simple enough to run asynchronously;
- Then the “join” part begins.
The results of all sub-tasks are recursively joined into a single result.
In the case of a task that returns void, the program simply waits until every sub-task runs.
The ForkJoinPool is an implementation of the ExecutorService interface, so we can use it the same way as an ExecutorService.
Below is an example of submitting jobs and get execution results with ForkJoinPool:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.TimeUnit;
public class ForkJoinPoolTest1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ForkJoinPool pool = new ForkJoinPool(3);
try {
List<ForkJoinTask<String>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
tasks.add(pool.submit(() -> {
TimeUnit.SECONDS.sleep(1);
return Thread.currentThread().getName() + " : executing task " + taskNumber;
}));
}
for (ForkJoinTask<String> task : tasks) {
System.out.println(task.get());
}
} finally {
pool.shutdown();
}
}
}
Below is the output of above code snippet :
ForkJoinPool-1-worker-1 : executing task 0
ForkJoinPool-1-worker-2 : executing task 1
ForkJoinPool-1-worker-3 : executing task 2
ForkJoinPool-1-worker-3 : executing task 3
ForkJoinPool-1-worker-2 : executing task 4
ForkJoinPool-1-worker-1 : executing task 5
ForkJoinPool-1-worker-3 : executing task 6
ForkJoinPool-1-worker-1 : executing task 7
ForkJoinPool-1-worker-2 : executing task 8
ForkJoinPool-1-worker-1 : executing task 9
ForkJoinTask is the base type for tasks executed inside ForkJoinPool.
In practice, we use two subclasses of ForkJoinTask, RecursiveAction and RecursiveTask.
They both have an abstract method compute() in which the task’s logic is defined.
- the RecursiveAction is for void tasks that does not return any value, below is an example of displaying all the values of an array through divide and conquer approach :
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
import java.util.stream.IntStream;
public class ForkJoinPoolTest2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ForkJoinPool pool = ForkJoinPool.commonPool();
try {
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
CustomRecursiveAction action = new CustomRecursiveAction(numbers);
pool.execute(action);
} finally {
pool.shutdown();
}
Thread.currentThread().join();
}
static class CustomRecursiveAction extends RecursiveAction {
private int[] arr;
private static final int THRESHOLD = 3;
public CustomRecursiveAction(int[] arr) {
this.arr = arr;
}
@Override
protected void compute() {
if (arr.length > THRESHOLD) {
ForkJoinTask.invokeAll(createSubtasks());
} else {
processing(arr);
}
}
private List<CustomRecursiveAction> createSubtasks() {
List<CustomRecursiveAction> subtasks = new ArrayList<>();
subtasks.add(new CustomRecursiveAction(
Arrays.copyOfRange(arr, 0, arr.length / 2)));
subtasks.add(new CustomRecursiveAction(
Arrays.copyOfRange(arr, arr.length / 2, arr.length)));
return subtasks;
}
private void processing(int[] arr) {
IntStream.of(arr).forEach(System.out::println);
}
}
}
Below is the output of above code snippet :
8
9
10
3
4
5
1
2
6
7
- the RecursiveTask<V> is for tasks that return a value, below is an example of calculating the sum of all the values of an array through divide and conquer approach :
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.IntStream;
public class ForkJoinPoolTest3 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ForkJoinPool pool = ForkJoinPool.commonPool();
try {
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = IntStream.of(numbers).sum();
System.out.println("sum = " + sum);
CustomRecursiveTask task = new CustomRecursiveTask(numbers);
System.out.println("sum by fork join = " + pool.submit(task).get());
} finally {
pool.shutdown();
}
Thread.currentThread().join();
}
static class CustomRecursiveTask extends RecursiveTask<Integer> {
private int[] arr;
private static final int THRESHOLD = 5;
public CustomRecursiveTask(int[] arr) {
this.arr = arr;
}
@Override
protected Integer compute() {
if (arr.length > THRESHOLD) {
return ForkJoinTask.invokeAll(createSubtasks())
.stream()
.mapToInt(ForkJoinTask::join)
.sum();
} else {
return processing(arr);
}
}
private Collection<CustomRecursiveTask> createSubtasks() {
List<CustomRecursiveTask> dividedTasks = new ArrayList<>();
dividedTasks.add(new CustomRecursiveTask(
Arrays.copyOfRange(arr, 0, arr.length / 2)));
dividedTasks.add(new CustomRecursiveTask(
Arrays.copyOfRange(arr, arr.length / 2, arr.length)));
return dividedTasks;
}
private Integer processing(int[] arr) {
return Arrays.stream(arr).sum();
}
}
}
Below is the output of above code snippet :
sum = 55
sum by fork join = 55
ForkJoinPool vs ExecutorService – when to use which?
In practice ExecutorService is usually used to process many independent requests concurrently, and fork join pool for accelerating one huge divide and conquer job.
5. CompletionService
CompletionService allows the execution of asynchronous tasks and above all facilitates the retrieval of the results as the tasks are completed.
In below example, we use an ExecutorService to submit 10 tasks (task 0 to 9), each has an execution time from 1 to 10 seconds, and we retrieve the results in the order of task 9 to 0.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class CompletionServiceTest1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
try {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
futures.add(es.submit(
() -> {
TimeUnit.SECONDS.sleep(taskNumber);
return Thread.currentThread().getName() + " : executing task " + taskNumber;
}));
}
for (int i = futures.size() - 1; i >= 0; i--) {
System.out.println(futures.get(i).get());
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-1 : executing task 9
pool-1-thread-3 : executing task 8
pool-1-thread-2 : executing task 7
pool-1-thread-1 : executing task 6
pool-1-thread-3 : executing task 5
pool-1-thread-2 : executing task 4
pool-1-thread-1 : executing task 3
pool-1-thread-3 : executing task 2
pool-1-thread-2 : executing task 1
pool-1-thread-1 : executing task 0
As we can see that the task 9 takes the longest time, the result of other tasks can not be retrieved even if they are already finished execution, we have to wait until 10 seconds when task 9 is finished.
Now we use an CompletionService, instead of using get() of Future task, we use take() of CompletionService to retrieve the result.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class CompletionServiceTest2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(3);
ExecutorCompletionService<String> ecs = new ExecutorCompletionService<>(es);
try {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Integer taskNumber = i;
futures.add(ecs.submit(
() -> {
TimeUnit.SECONDS.sleep(taskNumber);
return Thread.currentThread().getName() + " : executing task " + taskNumber;
}));
}
for (int i = futures.size() - 1; i >= 0; i--) {
System.out.println(ecs.take().get());
}
} finally {
es.shutdown();
}
}
}
Below is the output of above code snippet :
pool-1-thread-1 : executing task 0
pool-1-thread-2 : executing task 1
pool-1-thread-3 : executing task 2
pool-1-thread-1 : executing task 3
pool-1-thread-2 : executing task 4
pool-1-thread-3 : executing task 5
pool-1-thread-1 : executing task 6
pool-1-thread-2 : executing task 7
pool-1-thread-3 : executing task 8
pool-1-thread-1 : executing task 9
As we can see that this time, the results are retrieved according to their execution time.
6. CompletableFuture
CompletableFuture for java is like Promises for javascript.
Both of them represent the eventual completion or failure of an asynchronous operation.
CompletableFuture class implements the Future interface so that we can use it as a Future implementation but with additional completion logic.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureTest1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> cf = new CompletableFuture<>();
ExecutorService pool = Executors.newSingleThreadExecutor();
try {
pool.submit(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
cf.complete(Thread.currentThread().getName() + " : hello world");
}
});
} finally {
pool.shutdown();
}
System.out.println(cf.get());
}
}
Below is the output of above code snippet :
pool-1-thread-1 : hello world
Methods with the Async postfix in CompletableFuture class are usually intended for running a corresponding execution step in another thread.
- The methods without the Async postfix run the next execution stage using a calling thread;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureTest2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture
.supplyAsync(() -> "[ 1. " + Thread.currentThread().getName() + " : supplyAsync = hello world ]")
.thenApply(g -> "[ 2. " + Thread.currentThread().getName() + " : thenApply = " + g + " ]")
.thenAccept(g -> System.out
.println("[ 3. " + Thread.currentThread().getName() + " : thenAccept = " + g + " ]"));
Thread.sleep(2000);
}
}
Below is the output of above code snippet :
[ 3. ForkJoinPool.commonPool-worker-1 : thenAccept = [ 2. ForkJoinPool.commonPool-worker-1 : thenApply = [ 1. ForkJoinPool.commonPool-worker-1 : supplyAsync = hello world ] ] ]
- The Async method without the Executor argument runs a step using the ForkJoinPool.commonPool();
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureTest3 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture
.supplyAsync(() -> "[ 1. " + Thread.currentThread().getName()
+ " : supplyAsync = hello world ]")
.thenApplyAsync(g -> "[ 2. " + Thread.currentThread().getName() + " : thenApplyAsync = "
+ g + " ]")
.thenAcceptAsync(g -> System.out
.println("[ 3. " + Thread.currentThread().getName()
+ " : thenAcceptAsync = " + g + " ]"));
Thread.sleep(3000);
}
}
Below is the output of above code snippet :
[ 3. ForkJoinPool.commonPool-worker-1 : thenAcceptAsync = [ 2. ForkJoinPool.commonPool-worker-1 : thenApplyAsync = [ 1. ForkJoinPool.commonPool-worker-1 : supplyAsync = hello world ] ] ]
- The Async method with an Executor argument runs a step using the passed Executor.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureTest4 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newSingleThreadExecutor();
try {
CompletableFuture
.supplyAsync(() -> "[ 1. " + Thread.currentThread().getName()
+ " : supplyAsync = hello world ]", es)
.thenApplyAsync(g -> "[ 2. " + Thread.currentThread().getName()
+ " : thenApplyAsync = " + g + " ]", es)
.thenAcceptAsync(g -> System.out
.println("[ 3. " + Thread.currentThread().getName()
+ " : thenAcceptAsync = " + g
+ " ]"),
es);
} finally {
Thread.sleep(3000);
es.shutdown();
}
}
}
Below is the output of above code snippet :
[ 3. pool-1-thread-1 : thenAcceptAsync = [ 2. pool-1-thread-1 : thenApplyAsync = [ 1. pool-1-thread-1 : supplyAsync = hello world ] ] ]
For error handling in a chain of asynchronous computation steps, the CompletableFuture class allows us to handle it in a special handle method.
This method receives two parameters :
the result of the computation (if it finished successfully) and the exception thrown (if some computation step did not complete normally).
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureTest5 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> {
int result = 1 / 0;
return Thread.currentThread().getName() + " : hello world";
})
.handle((g, t) -> {
if (t == null) {
return Thread.currentThread().getName() + " : " + g;
}
return Thread.currentThread().getName() + " : " + t.getCause().getMessage();
});
String greeting = f.get();
System.out.println(greeting);
}
}
Below is the output of above code snippet :
ForkJoinPool.commonPool-worker-1 : / by zero
Alternatively, we can also use the exceptionally method to handle only exceptional completion of the future.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureTest8 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> {
int result = 1 / 0;
return Thread.currentThread().getName() + " : hello world";
})
.exceptionally(error -> {
return Thread.currentThread().getName() + " : " + error.getCause().getMessage();
});
String greeting = f.get();
System.out.println(greeting);
}
}
Below is the output of above code snippet :
main : / by zero
The allOf static method of CompletableFuture allows to wait for the completion of all of the Futures.
This method does not return the combined results of all Futures.
Instead, we have to get results from Futures manually.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureTest6 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> f1 = CompletableFuture
.supplyAsync(() -> Thread.currentThread().getName() + " : hello");
CompletableFuture<String> f2 = CompletableFuture
.supplyAsync(() -> Thread.currentThread().getName() + " : world");
CompletableFuture<Void> f = CompletableFuture.allOf(f1, f2);
f.join();
String r1 = f1.isDone() ? f1.join() : "";
String r2 = f2.isDone() ? f2.join() : "";
System.out.println(r1);
System.out.println(r2);
}
}
Below is the output of above code snippet :
ForkJoinPool.commonPool-worker-1 : hello
ForkJoinPool.commonPool-worker-2 : world
The thenCompose method allows to chain two Futures sequentially.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureTest7 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> "hello")
.thenCompose((g) -> {
return CompletableFuture.supplyAsync(() -> g + " world");
});
String greeting = f.get();
System.out.println(greeting);
}
}
Below is the output of above code snippet :
hello world