Home » Thread Pool in Java Multi-Threading Explained

Thread Pool in Java Multi-Threading Explained

by Imran Shaikh
2047 views
Thread Pool in Java Threads Explained

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 thread pool in Java if we are building multi-threaded java application.

Threads in Java

Before we talk about thread pool in Java, let revisit threads. Threads are simple yet powerful things. By creating multiple threads, 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, 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


You can follow me on social @coderstea on TwitterLinkedinFacebook, or Instagram. We also share high-quality videos about programming on our Youtube channel. You can also publish your own post on CodersTea.com, just share your thought on Contact Us or let us know in the comments.


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 application. Most of the time we create so many threads that we are actually 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, 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() 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 too many threads that our CPU can handle, then CPU will get busy in context switching rather than processing the actual work.

Thread Lifecycle
Thread Lifecycle

Didn’t see this coming, did you? Don’t worry, we will discuss how you can at least make up for few drawbacks we just saw. So prepare your cup of tea to sip and manage threads.

How Does Thread Pooling in Java works

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 a simpler word, combining resources for sharing. In thread pooling or any pooling, you collect N number of thread or resource 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 the 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 the Threadpool in Java, Java provides a framework called Executor Framework. In this, we have an interface called Executor, 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);
}

Thread Pool in Java: ExecutorService

ExecutorService is a subinterface of the Executor interface. You can say its like a pro version to Executor interface. It has multiple methods that give us more control over tasks in threadpool. 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 basically 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 own post and we will do that in the future and update 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.

MethodDescription
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
Executors.java functions

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 case of newCachedThreadPool() 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.

Executor Service

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 had 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 ");
    }
}

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 complete to use the thread. In 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();
}

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

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. Other 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();
}

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

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 in 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 same as 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();
}

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

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. 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.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy
0
Would love your thoughts, please comment.x
()
x