Concurrency in Java: Thread pools, ExecutorService & Future

  • 4.1/5
  • 4851
  • Jul 20, 2024

A thread pool is a set of pre-allocated threads that are adept at executing tasks on demand.

Java threads are mapped to system-level threads, hence consuming the operating system's resources.

If we create threads uncontrollably, we may run out of these resources quickly. A thread pool helps save these system resources in a multithreaded application.

Using a thread pool, an application does not need to create a new thread each time a thread is required; this can significantly reduce resource consumption.

Why a thread pool?

Using a thread pool can offer a number of advantages over creating and managing threads yourself.

1) A thread pool provides a solution to the problem of thread cycle overhead by using previously created threads to execute current tasks.

2) Thread pools also provide a significant advantage by separating the execution of tasks from the creation and management of threads.

3) A thread pool can also be used for executing tasks in a controlled manner; for example, by limiting the number of concurrent executions or prioritizing certain tasks over others.

4) Thread pools can use a queue for tasks, which can help ensure that critical tasks are not delayed due to a lack of available threads.

Thread Pools in Java

The java.util.concurrent package provides a number of classes and interfaces that can be used to create a variety of "thread pools" in Java.

1) Executor interface

The "java.util.concurrent.Executor" interface has a single "void execute(Runnable command)" method to submit a Runnable instance for execution.

The command may execute in a new thread, in a pooled thread, or in the calling thread, at the discretion of the Executor implementation.

The "Executors" class provides convenient factory methods for these Executors.

pool-1-thread-1 Hello A !!!
pool-1-thread-1 Hello B !!!

In the code above, Executors.newSingleThreadExecutor() returns an instance of the implementation of "ExecutorService". The "ExecutorService" interface in turn extends the "Executor" interface itself.

2) ExecutorService interface

The "java.util.concurrent.ExecutorService" interface extends the "Executor" interface and also adds methods that help manage and control the execution of threads.

pool-1-thread-3 running !!!
pool-1-thread-2 running !!!
pool-1-thread-1 running !!!

In the code above, Executors.newFixedThreadPool(5) returns an instance of the "ThreadPoolExecutor" class.

For this instance of "ThreadPoolExecutor", the value for "corePoolSize" and "maximumPoolSize" is set to "5", and the values for other parameters are set to default, i.e., keepAliveTime=0, unit=miliseconds, workingQueue=LinkedBlockingQueue(), threadFactory=new DefaultThreadFactory() and handler=AbortPolicy().

The "ThreadPoolExecutor" class has different constructors, hence other factory methods in "Executors" can replace or change the above-mentioned default values.

keepAliveTime

Threads use this timeout when there are more threads than "corePoolSize" present or if "allowCoreThreadTimeOut" is set to "true". Otherwise, they wait forever for new work.

corePoolSize

Core pool size is the minimum number of workers to keep alive (and not allow to time out, etc.) unless allowCoreThreadTimeOut is set, in which case the minimum is zero.

maximumPoolSize

Maximum pool size.

2.1) Single thread pool

The "single thread pool" is an implementation of "ThreadPoolExecutor" that contains a single thread. In this, the "ThreadPoolExecutor" is decorated with an immutable wrapper, so it can't be reconfigured after creation.

In this thread pool, tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time.

pool-1-thread-1 running !!!
pool-1-thread-1 running !!!
pool-1-thread-1 running !!!

In the code above, Executors.newSingleThreadExecutor() returns a "wrapper" (i.e. FinalizableDelegatedExecutorService) instance of the "ThreadPoolExecutor" class with "corePoolSize" and "maximumPoolSize" set to "1".

2.2) Fixed thread pool

The "fixed thread pool" is an implementation of "ThreadPoolExecutor" that contains a specified fixed number of threads.

pool-1-thread-1 running !!!
pool-1-thread-3 running !!!
pool-1-thread-2 running !!!

In the code above, Executors.newFixedThreadPool(5) returns an instance of the "ThreadPoolExecutor" class with "corePoolSize" and "maximumPoolSize" set to "5".

2.3) Cached thread pool

The "cached thread pool" is an implementation of "ThreadPoolExecutor" and does not accept any parameter for the number of threads.

It creates new threads as needed but will reuse previously constructed threads when they are available. Threads that have not been used for "sixty seconds" are terminated and removed from the cache.

pool-1-thread-1 running !!!
pool-1-thread-2 running !!!
pool-1-thread-3 running !!!

In the code above, Executors.newCachedThreadPool() returns an instance of the "ThreadPoolExecutor" class with "corePoolSize=0", "maximumPoolSize=Integer.MAX_VALUE" and workingQueue=new SynchronousQueue().

In a SynchronousQueue, pairs of "insert" and "remove" operations always occur simultaneously. So, the queue never actually contains anything, and its size will always be zero.

3) ScheduledExecutorService interface

The "ScheduledExecutorService" interface extends the "ExecutorService" interface and also provides additional methods that can schedule commands to run after a given delay or to execute periodically.

The implementing class of "ScheduledExecutorService" is "ScheduledThreadPoolExecutor".

1) The method "public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit)" allows a task to run once after a specified delay.

2) The method "public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)" allows a task to run after a specified initial delay and then run it repeatedly with a certain period.

3) The method "public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)" is similar to scheduleAtFixedRate, but the specified delay is measured from the end of the previous task.

4) ForkJoinPool

The fork/join framework was introduced in Java 7; ForkJoinPool is the central part of this framework.

The "ForkJoinPool" is an implementation of the "ExecutorService" that manages worker threads and provides us with tools to get information about the thread pool's state and performance.

It implements the work-stealing algorithm (free threads try to "steal" work from busy threads).

The framework first "forks" or recursively breaks the task into smaller independent subtasks until they are simple enough to run asynchronously.

Next, the "join" part begins, in which the results of all subtasks are recursively joined into a single result. In the case of a task that returns void, the program simply waits until every subtask runs.

In Java 8, the most convenient way to get access to the instance of the ForkJoinPool is to use its static method commonPool().

Available cores: 8
Thread: main
Thread: main
Thread: ForkJoinPool.commonPool-worker-1
Thread: ForkJoinPool.commonPool-worker-2
Thread: ForkJoinPool.commonPool-worker-1
Thread: ForkJoinPool.commonPool-worker-1
Result: 150
Pool Size: 5

Future

The java.util.concurrent.Future class represents a future result of an asynchronous computation that will eventually appear in the 'Future' after the processing is complete.

areaFutureA calculation is running ... and areaFutureB calculation is running ...
areaFutureA calculation is running ... and areaFutureB calculation is running ...
areaFutureA calculation is running ... and areaFutureB calculation is running ...
areaFutureA calculation is running ... and areaFutureB calculation is running ...
AreaA: 200, AreaB: 375
Calculation is complete !!!

boolean isDone()

It tells us if the executor has finished processing the task and returns "true" or "false" accordingly.

V get()

It returns the actual result from the calculation. The method get() blocks the execution until the task is complete.

V get(long timeout, TimeUnit unit)

It is an overloaded version that takes a timeout and a TimeUnit as arguments. It throws a TimeoutException if the task doesn't return before the specified timeout period.

boolean cancel(boolean mayInterruptIfRunning)

It attempts to cancel the execution of this task. This method has no effect if the task has already been completed, cancelled, or could not be cancelled for some other reason.

If the task has not started when cancel is called, it should never run.

The "mayInterruptIfRunning" parameter, if "true,"tries to interrupt the thread executing the task (if the thread is known to the implementation), otherwise, in-progress tasks are allowed to complete.

It also returns a boolean value: "false" if the task could not be cancelled; "true" otherwise.

Index
Modern Java - What’s new in Java 9 to Java 17

32 min

What is differences between JDK, JVM and JRE ?

2 min

What is ClassLoader in Java ?

2 min

Object Oriented Programming (OOPs) Concept

17 min

Concurrency in Java: Creating and Starting a Thread

12 min

Concurrency in Java: Interrupting and Joining Threads

5 min

Concurrency in Java: Race condition, critical section, and atomic operations

13 min

Concurrency in Java: Reentrant, Read/Write and Stamped Locks

11 min

Concurrency in Java: "synchronized" and "volatile" keywords

10 min

Concurrency in Java: using wait(), notify() and notifyAll()

6 min

Concurrency in Java: What is "Semaphore" and its use?

2 min

Concurrency in Java: CompletableFuture and its use

18 min

Concurrency in Java: Producer-consumer problem using BlockingQueue

2 min

Concurrency in Java: Producer-Consumer Problem

2 min

Concurrency in Java: Thread pools, ExecutorService & Future

14 min

Java 8 Lambdas, Functional Interface & "static" and "default" methods

28 min

Method Reference in Java (Instance, Static, and Constructor Reference)

9 min

What’s new in Java 21: A Tour of its Most Exciting Features

14 min

Java Memory Leaks & Heap Dumps (Capturing & Analysis)

9 min

Memory footprint of the JVM (Heap & Non-Heap Memory)

15 min