HomeJavaUnderstanding Java Thread Pool with Example

Understanding Java Thread Pool with Example

In this journal entry, “Understanding Java Thread Pool”, we will be answering to the question “Why the application need a thread pool?”. Well the answer to this is when we develop a simple application in Java which needs some concurrent tasks to execute, we simply create some Runnable objects and then create the corresponding Thread objects to execute them. Thread creation in Java is an expensive operation and every time when we create and start a new thread instance, application performance may go for a toss. A clear understanding of java thread pools helps us to cater these problems.

Related Journal Entries

Understanding Java Synchronized Keyword
Top 10 Java Collections Interview Questions and Answers
How to Monitor Disk, Memory, and Threads in Java?

Java Thread Pool

A thread pool is basically a collection of thread which are pre-initialized. Generally the collection size is fixed, but it is not a mandatory thing. Using thread pool facilitates the N number of tasks execution using the same threads. If there are more tasks than the size of the thread pool, then tasks have to wait in a queue like structure (FIFO – First In First Out).

If the tasks execution is completed by anyone of the thread, then that thread can pickup a new task from the queue and start executing it. Once all the tasks are completed, threads remain active and a watcher keep watching queue for any new tasks. As soon as tasks come, threads again start picking up tasks and execute them.

ThreadPoolExecutor

Since Java 5, the Java concurrency API provides a mechanism Executor framework. Java Executor Framework (java.util.concurrent.Executor) is used to run the Runnable objects without creating new threads every time and mostly re-using the already created threads. This works around ThreadPoolExecutor class which implements the Executor interface, its sub-interface ExecutorService.

ThreadPoolExecutor separates the task creation and its execution. With ThreadPoolExecutor, you only have to implement the Runnable objects and send them to the executor. It is the responsibility of the ThreadPoolExecutor for their execution, instantiation, and running with necessary threads.

There are 5 different ways for creating a ThreadPoolExecutor:

#1 Fixed thread pool executor

Creates a thread pool with a fixed number of threads and reuses them to execute any number of tasks. When all the threads are active and if additional tasks are submitted, then they will have to wait in the queue until a thread is available. It is best fit for most off the real-life use-cases.

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);

#2 Cached thread pool executor

Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. DO NOT use this thread pool if tasks are long running. It can bring down the system if number of threads goes beyond what system can handle.

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();

#3 Scheduled thread pool executor

Creates a thread pool that can schedule commands to run after a specified delay, or periodically.

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(10);

#4 Single thread pool executor

As the name suggests, it creates a single thread to execute all tasks. Use this only when you have one task to execute.

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newSingleThreadExecutor();

#5 Work stealing thread pool executor

In Java 8, a new type of thread pool is introduced as newWorkStealingPool() to complement the existing ones. Java gave a very succinct definition of this pool as:

“Creates a work-stealing thread pool using all available processors as its target parallelism level.”

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newWorkStealingPool(4);

ThreadPoolExecutor Example

Create class implementing Runnable Interface

Let’s create a class Message which will take random time to complete it, everytime.

package in.developersjournal.threadpool;
 
import java.util.concurrent.TimeUnit;
 
public class Message implements Runnable {
    private String messageString;
 
    public Message(String messageString) {
        this.messageString = messageString;
    }
 
    public String getMessage() {
        return messageString;
    }
 
    public void run() {
        try {
            Long duration = (long) (Math.random() * 10);
            System.out.println("Message in Runnable: " + messageString);
            TimeUnit.SECONDS.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Create the main class with Thread Pool Executor

package in.developersjournal.threadpool;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
 
public class ThreadPoolExecutorExample
{
    public static void main(String[] args)
    {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
         
        for (int i = 1; i <= 10; i++)
        {
            Message message = new Message("Message: " + i);
            System.out.println("Message Created : " + message.getMessage());
 
            executor.execute(message);
        }
        executor.shutdown();
    }
}

Output of the above code

Message Created : Message: 1
Message Created : Message: 2
Message Created : Message: 3
Message Created : Message: 4
Message Created : Message: 5
Message Created : Message: 6
Message Created : Message: 7
Message Created : Message: 8
Message Created : Message: 9
Message Created : Message: 10
Message in Runnable: Message: 3
Message in Runnable: Message: 4
Message in Runnable: Message: 5
Message in Runnable: Message: 1
Message in Runnable: Message: 2
Message in Runnable: Message: 6
Message in Runnable: Message: 8
Message in Runnable: Message: 7
Message in Runnable: Message: 10
Message in Runnable: Message: 9

Points to Remember

  • Once you create the ThreadPoolExecutor class object you have to end it explicitly. If we forgot to end this, the executor will continue with the execution phase and the application or the program won’t end. We as developers have to make sure that our Java application ends smoothly because any Java application won’t end itself until all of its non-daemon threads finish their execution. So always remember to terminate the executor.
  • Use the shutdown() method of the ThreadPoolExecutor class, to indicate that the work is finished now. Once all the pending tasks are finished by the executor, then it finishes the execution. However, if you try to send another task to the executor once the shutdown() method has been called, the task will be rejected and the executor will throw a RejectedExecutionException exception.
  • A lot of methods are provided by the ThreadPoolExecutor class to get the information about its status:
    # getPoolSize(): Information about the size of the pool
    # getActiveCount(): Number of threads
    # getCompletedTaskCount(): Number of completed tasks by the executor.
    # getLargestPoolSize(): Maximum number of threads that have been in the pool at a time.
    # shotdownNow(): Shutdowns the executor immediately. Stops the execution of all the pending tasks and returns a list of those tasks. However, the point to note here is that the tasks that are running continue with their execution, but the method doesn’t wait for their finalization.
    # isTerminated(): This method returns true if you have called the shutdown() or shutdownNow() methods and the executor finishes the process of shutting it down.
    # isShutdown(): This method returns true if you have called the shutdown() method of the executor.
    # awaitTermination(long timeout, TimeUnitunit): This method blocks the calling thread until the tasks of the executor have ended or the timeout occurs. The TimeUnit class is an enumeration with the following constants: DAYS, HOURS, MICROSECONDS etc.
RELATED ARTICLES

Most Popular