Skip to main content

Concurrency in Java: Thread pools, ExecutorService & Future

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.

Comments

Popular posts from this blog

Deploying Spring Boot microservices on Kubernetes Cluster

This article guides you through the deployment of two Spring Boot microservices, namely "order-service" and "inventory-service," on Kubernetes using "MiniKube" . We will establish communication between them, with "order-service" making calls to an endpoint in "inventory-service." Additionally, we will configure "order-service" to be accessible from the local machine's browser . 1) Create Spring Boot microservices The Spring Boot microservices, "order-service" and "inventory-service," have been developed and can be found in this GitHub repository. If you are interested in learning more about creating Spring Boot REST microservices, please refer to this or this (Reactive) link. 2) Build Docker Images The Docker images for both "order-service" and "inventory-service" have already been generated and deployed on DockerHub, as shown below. codeburps/order-service cod...

Circuit Breaker Pattern with Resilience4J in a Spring Boot Application

Read Also: Spring Cloud Circuit Breaker + Resilience4j Resilience4j is a lightweight fault tolerance library that draws inspiration from Netflix Hystrix but is specifically crafted for functional programming. The library offers higher-order functions, known as decorators , designed to augment any functional interface, lambda expression, or method reference with features such as Circuit Breaker, Rate Limiter, Retry, or Bulkhead . These functionalities can be seamlessly integrated within a project, class, or even applied to a single method. It's possible to layer multiple decorators on any functional interface, lambda expression, or method reference, allowing for versatile and customizable fault tolerance. While numerous annotation-based implementations exist online, this article focuses solely on the reactive approach using router predicates and router functions . How Circuit Breaker Pattern works? In general, a circuit breaker functions as an automatic electrical s...

How to create a basic Spring 6 project using Maven

Below is a step-by-step guide to creating a basic Spring project using Maven. 1) Create a Maven Project Use the following Maven command to create a new Maven project. mvn archetype:generate -DgroupId=com.tb -DartifactId=spring-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false 2) Import in IntelliJ IDEA If you haven't already, open IntelliJ IDEA on your system. Go to "File" > "New" > "Project from Existing Sources..." . In the file dialog, navigate to the directory where your Maven project is located. Select the pom.xml file within the project directory and click "Open." 3) Update pom.xml In total, the application requires the below-mentioned dependencies: 4) Create Spring Configuration Create a Java configuration class that uses annotations to define your Spring beans and their dependencies. This class should be annotated with @Configuration . 5) Create the Main Application C...