Recycle the Threads and Save the Resources with Thread Pool

Before we begin to talk about thread pool, let revisit threads. Threads are really powerful things. By creating multiple threads, multithreading, you can do tasks simultaneously. Wow, how cool and how efficient, right? But wait, if there is a heaven there is a hell. Multithreading does come with thread management overhead for the developer such as creating multiple threads, the synchronicity between them, deadlock, and most importantly how really efficient it is.

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

The Hell

An efficient and superfast program is the dream of every programmer. To make this happen we usually end up using threads, multithreading precisely. Most of the time we create so many threads that we are actually decreasing the speed instead of making it faster. How? Creating so many threads is a very expensive process and it can delay 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 again and again. Resulting thread creation overhead. And there are other factors as well such as your CPU. If we end up too many threads that our COU can handle, most of the time gets spent into context switching. So how will you avoid these multiple new thread creation? Just sip your tea and read. For the code, you can find it on GitHub here or the full project here

Recycling is good for the environment: Thread Pool

Recycling is good for our environment since the resources are limited on our planet.

Similarly, our computer resources are also limited. So, why not reuse them and save efficiency? Reuse the thread, we used already used, instead of creating a new one?

How Does Thread Pooling or Any Pooling Works

What is pooling by the way? In a simpler word, combining resources for sharing. Examples can be your Uber pool, printer sharing in your office, Dynamic Host Configuration Protocol (DHCP), JDBC connection pooling, and many more.

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 you put it back into the pool after completing your work. Well, its 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.

Thread Pool continued

Now that we know about the pooling, let us jump to the actual working of thread pooling in java. To use the Threadpool in Java, Java provides a framework called Executor Framework which is an interface called Executor and its subinterface ExecutorService and the classes implementing them. Don’t worry its not another dependency. It is in the package java.util.concurrent. The Executor contains only one abstract method called execute(Runnable task). Through this, you can execute your tasks. Tasks are just Runnable objects.

public interface Executor {
    void execute(Runnable command);
}

Thread Pool ExecutorService

ExecutorService is a subinterface of the Executor interface. It is a pro version you can say. It has multiple methods that give us more control over tasks. 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 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

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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *