Hey, Tea Lovers! Today we will talk about the Thread Pool in Java. And how it saves resources and increases performance. And also, why we should use a thread pool in Java if we are building a multi-threaded java application?
Threads in Java
Before we talk about the thread pool in Java, let’s revisit threads. Threads are simple yet powerful things. By creating multiple threads, and multithreading applications, you can do tasks simultaneously. Wow, how cool and how efficient, right? But wait, if there is a heaven there is a hell.
Multithreading application build comes with thread management overhead for the developer. Such as creating multiple threads, the synchronicity between them, and deadlock among other things.
In this post, we will talk about how multithreading affects your system without you knowing it and how to overcome certain parts of it and make it more efficient and that includes thread reusing or recycling.
If you want to learn about multithreading in java or want to refresh the basics you can see my previous post “How MultiThreading Lets You Drive A Car, Ride The Bikes, And Sail A Boat Simultaneously“
I would be happy to connect with you guys on social media. It’s @coderstea on Twitter, Linkedin, Facebook, Instagram, and YouTube.
Please Subscribe to the newsletter to know about the latest posts from CodersTea.
The Hell of Thread Management in Java
An efficient and superfast program is the dream of every programmer. To make this happen we usually end up using threads i.e multithreading applications. Most of the time we create so many threads that we are lowering the speed instead of making it faster. How? Multithreading application build comes with thread management overhead for developers. Such as creating multiple threads, the synchronicity between them, and deadlock among other things.
Creating many threads is a very expensive process and it can delay or limit the actual process, which you wanted to avoid in the first place. In Java, threads are mapped to system-level threads, so over-numbering them can empty your resources.
Once the run()
the method finishes, we never use that thread again and end up creating new threads, every time we need a thread. This results in thread creation overhead. There are other factors as well such as your CPU. If we end up with too many threads that our CPU can handle, then the CPU will get busy in context switching rather than processing the actual work.
Didn’t see this coming, did you? Don’t worry, we will discuss how you can at least make up for a few drawbacks we just saw. So prepare your cup of tea to sip and manage threads.
How Does Thread Pooling in Java work
Recycling is good for our environment as the resources are limited on our planet.
Similarly, our computer resources are also limited. So, why not reuse them and save efficiency?
What is pooling by the way? In simpler words, combining resources for sharing. In thread pooling or any pooling, you collect N number of threads or resources in one place. You pick one thread from it, use it, and put it back into the pool after completing your work. Well, it’s the simplest way of explaining it. There can be certain scenarios like all the resources are busy when you went to pick it up, you wait or add one more resource if possible. These are the basic condition and scenarios of any pooling system.
Examples can be your printer sharing in your office, Dynamic Host Configuration Protocol (DHCP), JDBC connection pooling, or Carpooling can also be something similar.
Executor Framework for Thread Pool in Java
Now that we know about pooling, let us jump to the actual working of thread pooling in java. I will briefly talk about the different classes and interfaces used to create and manage thread pools in Java. Then I will show you an abstract overview with a diagram, you can directly jump to the topic, click here.
To use Threadpool in Java, Java provides a framework called Executor Framework. In this, we have an interface called, its subinterface ExecutorService
and the classes implementing them. Don’t worry, it’s not another dependency. Executor Framework is in the Java package java.util.concurrent
.
The Executor
contains only one abstract method called execute(Runnable task)
, through which, you can execute your tasks. Tasks are just Runnable
objects. It’s simple, just pass the runnable object, which contains what you want to do, and it will run it for you.
public interface Executor {
void execute(Runnable command);
}
Code language: PHP (php)
Thread Pool in Java: ExecutorService
ExecutorService is a sub-interface of the Executor interface. You can say it’s like a pro version of the Executor interface. It has multiple methods that give us more control over tasks in the thread pool. To execute a task you can use the execute(Runnable task)
method.
There is a method submit(..)
that works similarly to execute
but returns a Future<T>
, which as the name suggests gives you a future value. Future
says that “I may or may not have the value right now but it could be available in the future if the task is completed and produces some result”. It deserves its post and we will do that in the future and update it here.
But how can you create the object or thread pool of ExecutorService
? Via Executors
class.
Thread Pool Executors
Executors
is a kind of factory/utility class for the ExecutorService
. It creates the objects of ExecutorService
. There are various ways you can create different types of pools.
Method | Description |
---|---|
newSingleThreadExecutor() | Creates a single thread. |
newFixedThreadPool(int size) | Creates a fixed-size thread pool. |
newCachedThreadPool() | Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads if they are available |
In the case of newFixedThreadPool(int size)
& newSingleThreadExecutor()
the threads need to wait if the size is full and no thread is available to use. But in the case of newCachedThreadPool()
the new task doesn’t need to wait if the thread is not available.
Make sure to use
shutdown()
after all the task has been assigned or the program will not stop as the thread pool will still be active.
Basic Workflow of Thread Pool in Java
Let’s look at the abstract full view here. In the following diagram, you can see 3 different blocks. First Thread Pool is created with the help of Executors factory methods, say with size 5. Now, once you submit a task, the Executor Service will add your task to a queue. This queue act as a buffer for the tasks. Since there might be the possibility that there might be no thread available at the moment. Now, if any thread is available, then It assigns a thread to the task in the queue. Once the task is done, the thread gets available to the pool and the process continues for other submitted tasks.
Understand Java ThreadPool With Examples
Let us rewrite the example from “ How MultiThreading Lets You Drive A Car, Ride The Bikes, And Sail A Boat Simultaneously” using the thread pool. In the example, you have Boats, cars, and bikes. In the previous one, we created a new thread for each vehicle. Now we will use and understand the working and use case of each thread pool creation method in the above table.
The tasks: Car, Bike, and the Boat
public class ThreadPool {
static void pring5Times(String statement) {
for (int i = 0; i < 5; i++) {
System.out.println(statement + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Car1 implements Runnable {
@Override
public void run() {
ThreadPool.pring5Times("I am driving a Car on road ");
}
}
class Bike1 implements Runnable {
@Override
public void run() {
ThreadPool.pring5Times("I am riding a Bike on road ");
}
}
class Boat1 implements Runnable {
@Override
public void run() {
ThreadPool.pring5Times("I am Sailing the boat on sea ");
}
}
Code language: PHP (php)
These are the Runnable
objects we will be using. And there is one utility function, pring5Times(String statement)
to print the given statement 5 times. Let us see each function one by one.
Executors.newSingleThreadExecutor()
It creates only one thread in the pool and Each task needs to wait for the previous one to be complete to use the thread. This sequential execution takes place.
static void newSingleThreadExecutor() {
System.out.println("Running Executors.newSingleThreadExecutor()");
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(new Car1());
/* bike and boat will wait till car driving is completed. */
singleThreadPool.execute(new Bike1());
singleThreadPool.execute(new Boat1());
/* make sure to shitdown. */
singleThreadPool.shutdown();
}
Code language: JavaScript (javascript)
Output
Running Executors.newSingleThreadExecutor()
I am driving a Car on road 0
I am driving a Car on road 1
I am driving a Car on road 2
I am driving a Car on road 3
I am driving a Car on road 4
I am riding a Bike on road 0
I am riding a Bike on road 1
I am riding a Bike on road 2
I am riding a Bike on road 3
I am riding a Bike on road 4
I am Sailing the boat on sea 0
I am Sailing the boat on sea 1
I am Sailing the boat on sea 2
I am Sailing the boat on sea 3
I am Sailing the boat on sea 4
Code language: CSS (css)
As you can see threads are running one by one since there is only one thread to be shared.
Executors.newFixedThreadPool(int limit)
This creates a poll with the given number of threads i.e limit
. The number of tasks that will run in parallel is the given limit. Another task needs to wait for a thread to complete. So in our example, we have created a pool with size 2. So, Car
and Bike
will run parallelly or simultaneously but the Boat needs to wait for either of them to complete.
static void newFixedThreadPool(){
System.out.println("Running Executors.newFixedThreadPool(2)");
ExecutorService fixedSizeThreadPool = Executors.newFixedThreadPool(2);
fixedSizeThreadPool.execute(new Car1());
fixedSizeThreadPool.execute(new Bike1());
/* this time Since threadpool only has 2 thread,
Boat will wait till car driving and bike riding is completed. */
fixedSizeThreadPool.execute(new Boat1());
fixedSizeThreadPool.shutdown();
}
Code language: JavaScript (javascript)
Output
Running Executors.newFixedThreadPool(2)
I am driving a Car on road 0
I am riding a Bike on road 0
I am driving a Car on road 1
I am riding a Bike on road 1
I am driving a Car on road 2
I am riding a Bike on road 2
I am driving a Car on road 3
I am riding a Bike on road 3
I am driving a Car on road 4
I am riding a Bike on road 4
I am Sailing the boat on sea 0
I am Sailing the boat on sea 1
I am Sailing the boat on sea 2
I am Sailing the boat on sea 3
I am Sailing the boat on sea 4
Code language: CSS (css)
The bike and Car ran simultaneously but the boat needed to wait since only 2 threads were available.
Executors.newCachedThreadPool()
This method creates a unique and dynamic pool. If the thread is available it gets assigned to the task if not, a thread is added to the pool. In this, a thread does not wait for other threads to complete. The best suitable scenario would be when you have small tasks that need to be run simultaneously.
The Tasks are the same as in the above example but this time all three of the tasks, Car
, Bike
, and Boat
would run simultaneously.
static void newCachedThreadPool(){
System.out.println("Running Executors.newCachedThreadPool()");
ExecutorService chachedThreadPool = Executors.newCachedThreadPool();
chachedThreadPool.execute(new Car1());
chachedThreadPool.execute(new Bike1());
chachedThreadPool.execute(new Boat1());
chachedThreadPool.shutdown();
}
Code language: JavaScript (javascript)
Output
Running Executors.newCachedThreadPool()
I am driving a Car on road 0
I am riding a Bike on road 0
I am Sailing the boat on sea 0
I am driving a Car on road 1
I am riding a Bike on road 1
I am Sailing the boat on sea 1
I am driving a Car on road 2
I am riding a Bike on road 2
I am Sailing the boat on sea 2
I am driving a Car on road 3
I am riding a Bike on road 3
I am Sailing the boat on sea 3
I am driving a Car on road 4
I am riding a Bike on road 4
I am Sailing the boat on sea 4
Code language: CSS (css)
As you can see, you were able to drive, ride and sail simultaneously.
Conclusion
We have looked into the bad side of multithreading and its overhead on the system, what is thread pool and how to use them, type of thread pool is. I hope you enjoyed the post. In the next post on concurrency, we will talk about Future
and CompletableFuture
. You can find the code on GitHub here or the full project here.
I would be happy to connect with you guys on social media. It’s @coderstea on Twitter, Linkedin, Facebook, Instagram, and YouTube.
Please Subscribe to the newsletter to know about the latest posts from CodersTea.