Java Thread Pool: Efficient Multithreading with Reusable Worker Threads

Understand Java thread pools, a powerful feature for managing a fixed group of reusable worker threads. Learn how thread pools improve efficiency by assigning tasks to idle threads, maximizing resource usage and reducing overhead in multithreaded applications.



Java Thread Pool

Java Thread pool represents a group of worker threads that are waiting for the job and reused many times.

In the case of a thread pool, a group of fixed-size threads is created. A thread from the thread pool is pulled out and assigned a job by the service provider. After completion of the job, the thread is contained in the thread pool again.

Thread Pool Methods

  • newFixedThreadPool(int s): Creates a thread pool of the fixed size s.
  • newCachedThreadPool(): Creates a new thread pool that creates new threads when needed but uses previously created threads when available.
  • newSingleThreadExecutor(): Creates a new single-threaded executor.

Advantages of Java Thread Pool

  • Better performance: Saves time by reusing threads instead of creating new ones.
  • Real-time usage: Commonly used in Servlet and JSP where the container creates a thread pool to process requests.

Example of Java Thread Pool

Let's see a simple example of a Java thread pool using ExecutorService and Executors.

WorkerThread.java

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  

class WorkerThread implements Runnable {  
    private String message;  
    public WorkerThread(String s){  
        this.message=s;  
    }  
    public void run() {  
        System.out.println(Thread.currentThread().getName()+" (Start) message = "+message);  
        processMessage(); // call processMessage method that sleeps the thread for 2 seconds  
        System.out.println(Thread.currentThread().getName()+" (End)"); // prints thread name  
    }  
    private void processMessage() {  
        try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }  
    }  
}  
        
TestThreadPool.java

public class TestThreadPool {  
    public static void main(String[] args) {  
        ExecutorService executor = Executors.newFixedThreadPool(5); // creating a pool of 5 threads  
        for (int i = 0; i < 10; i++) {  
            Runnable worker = new WorkerThread("" + i);  
            executor.execute(worker); // calling execute method of ExecutorService  
        }  
        executor.shutdown();  
        while (!executor.isTerminated()) { }  

        System.out.println("Finished all threads");  
    }  
}
        
Output

pool-1-thread-1 (Start) message = 0
pool-1-thread-2 (Start) message = 1
pool-1-thread-3 (Start) message = 2
pool-1-thread-5 (Start) message = 4
pool-1-thread-4 (Start) message = 3
pool-1-thread-2 (End)
pool-1-thread-2 (Start) message = 5
pool-1-thread-1 (End)
pool-1-thread-1 (Start) message = 6
pool-1-thread-3 (End)
pool-1-thread-3 (Start) message = 7
pool-1-thread-4 (End)
pool-1-thread-4 (Start) message = 8
pool-1-thread-5 (End)
pool-1-thread-5 (Start) message = 9
pool-1-thread-2 (End)
pool-1-thread-1 (End)
pool-1-thread-4 (End)
pool-1-thread-3 (End)
pool-1-thread-5 (End)
Finished all threads
        

In this example, a pool of 5 threads is created, and each thread executes a task represented by WorkerThread.

ThreadPoolExample.java

Tasks.java

import java.util.Date;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.text.SimpleDateFormat;  

class Tasks implements Runnable {  
    private String taskName;  

    public Tasks(String str) {  
        taskName = str;  
    }  

    public void run() {  
        try {  
            for (int j = 0; j <= 5; j++) {  
                if (j == 0) {  
                    Date dt = new Date();  
                    SimpleDateFormat sdf = new SimpleDateFormat("hh : mm : ss");  
                    System.out.println("Initialization time for the task name: "+ taskName + " = " + sdf.format(dt));  
                } else {  
                    Date dt = new Date();  
                    SimpleDateFormat sdf = new SimpleDateFormat("hh : mm : ss");  
                    System.out.println("Time of execution for the task name: " + taskName + " = " +sdf.format(dt));  
                }  
                Thread.sleep(1000); // 1000ms = 1 sec  
            }  
            System.out.println(taskName + " is complete.");  
        } catch (InterruptedException ie) {  
            ie.printStackTrace();  
        }  
    }  
}  
        
ThreadPoolExample.java

public class ThreadPoolExample {  
    static final int MAX_TH = 3; // Maximum number of threads in the thread pool  
    
    public static void main(String[] args) {  
        Runnable rb1 = new Tasks("task 1");  
        Runnable rb2 = new Tasks("task 2");  
        Runnable rb3 = new Tasks("task 3");  
        Runnable rb4 = new Tasks("task 4");  
        Runnable rb5 = new Tasks("task 5");   
        
        ExecutorService pl = Executors.newFixedThreadPool(MAX_TH); // creating a thread pool with MAX_TH number of threads  
        
        pl.execute(rb1);  
        pl.execute(rb2);  
        pl.execute(rb3);  
        pl.execute(rb4);  
        pl.execute(rb5);  
        
        pl.shutdown(); // pool is shutdown  
    }  
}  
        
Output

Initialization time for the task name: task 1 = 06 : 13 : 02
Initialization time for the task name: task 2 = 06 : 13 : 02
Initialization time for the task name: task 3 = 06 : 13 : 02
Time of execution for the task name: task 1 = 06 : 13 : 04
Time of execution for the task name: task 2 = 06 : 13 : 04
Time of execution for the task name: task 3 = 06 : 13 : 04
Time of execution for the task name: task 1 = 06 : 13 : 05
Time of execution for the task name: task 2 = 06 : 13 : 05
Time of execution for the task name: task 3 = 06 : 13 : 05
Time of execution for the task name: task 1 = 06 : 13 : 06
Time of execution for the task name: task 2 = 06 : 13 : 06
Time of execution for the task name: task 3 = 06 : 13 : 06
Time of execution for the task name: task 1 = 06 : 13 : 07
Time of execution for the task name: task 2 = 06 : 13 : 07
Time of execution for the task name: task 3 = 06 : 13 : 07
Time of execution for the task name: task 1 = 06 : 13 : 08
Time of execution for the task name: task 2 = 06 : 13 : 08
Time of execution for the task name: task 3 = 06 : 13 : 08
task 2 is complete.
Initialization time for the task name: task 4 = 06 : 13 : 09
task 1 is complete.
Initialization time for the task name: task 5 = 06 : 13 : 09
task 3 is complete.
Time of execution for the task name: task 4 = 06 : 13 : 10
Time of execution for the task name: task 5 = 06 : 13 : 10
Time of execution for the task name: task 4 = 06 : 13 : 11
Time of execution for the task name: task 5 = 06 : 13 : 11
Time of execution for the task name: task 4 = 06 : 13 : 12
Time of execution for the task name: task 5 = 06 : 13 : 12
Time of execution for the task name: task 4 = 06 : 13 : 13
Time of execution for the task name: task 5 = 06 : 13 : 13
Time of execution for the task name: task 4 = 06 : 13 : 14
Time of execution for the task name: task 5 = 06 : 13 : 14
task 4 is complete.
task 5 is complete.
        

This example demonstrates tasks executed sequentially in a thread pool of maximum 3 threads.

Risks Involved in Thread Pools

  • Deadlock: Can occur when threads are waiting indefinitely for resources held by other threads.
  • Thread Leakage: Threads not returning to the pool after execution, leading to resource depletion.
  • Resource Thrashing: Wasted resources due to excessive context switching when pool size is too large.

Points to Remember

  • Avoid queuing tasks that wait indefinitely for results from other tasks to prevent deadlocks.
  • Explicitly shutdown the thread pool to terminate the executor and avoid resource leaks.
  • Tune the thread pool size based on available processors and task characteristics for optimal performance.

Conclusion

A thread pool is a powerful tool for managing threads efficiently in applications, particularly on servers. While conceptually straightforward, it requires careful consideration to avoid pitfalls and maximize performance.