Overview

Programs that run on databases and web servers keep executing requests from multiple clients continuously. These programs are designed to process a large number of very short tasks. One of the approaches to building a server application is to create a new thread each time a new request is received and service this new request in the just created thread. Although this approach seems right and very simple, it comes with some significant disadvantages. A server that would create a new thread for every request would consume more time and system resources in just creating and destroying threads repeatedly than on processing actual requests. Also, the JVM creating this many threads in a short time will quickly run out of memory. Due to these reasons, It is extremely important to limit the number of threads being created by JVM. This is where the Java thread pool comes into play.

Java thread pool

A thread pool in Java reuses the previously created threads to execute new requests. This also solves the problem of thread cycle overhead and running out of resources. Since the threads already exist, when a new request arrives, the delay due to the creation of a new thread is now eliminated, making the application significantly more responsive. Additionally, using Java thread pools can make it easier to control the number of threads that are active at a time.

Java thread pool implementation

Java provides a framework called Executor. It consists of the Executor interface, its sub-interface called ExecutorService, and the class, ThreadPoolExecutor. This class implements both of these interfaces.

To implement a thread pool in Java, first, an object of ExecutorService is created and a set of tasks is passed to it. ThreadPoolExecutor class allows users to set the core and maximum pool size. The requests that are supposed to be run by a particular thread are then executed sequentially.

Following are the methods used to create a thread pool and set the number of threads:

  • newFixedThreadPool(int): It creates a fixed-size thread pool.
  • newCachedThreadPool(): It creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available
  • newSingleThreadExecutor() : It generates a single thread.

While implementing a fixed thread pool, if all threads are currently run by the executor then all the pending requests are then placed in a queue and are executed when a thread is free.

The code mentioned below demonstrates a basic example of a thread pool executor with FixedThreadPool.

These steps are to be followed:

  1. Create a Runnable Object (request) to be executed
  2. Create an Executor Pool using Executors
  3. Pass the requests to the Executor Pool
  4. Lastly, shutdown the Executor Pool
1.import java.text.SimpleDateFormat;
2.import java.util.Date;
3.import java.util.concurrent.ExecutorService;
4.import java.util.concurrent.Executors;
5.
6. // Step 1 : Create a Runnable Object (request) to be executed
7. class Task01 implements Runnable  
8. {
9.    private String str;
10.
11.    public myTask(String s)
12.    {
13.        str = s;
14.    }    
15.    // Prints myTask str and sleeps for 1s
16    // This Whole process will be repeated 3 times
17.    public void run()
18.    {
19.        try
20.        {
21            for (int i = 0; i<=3; i++)
22.            {
23.                if (i==0)
24.                {
25.                    Date d = new Date();
26.                    SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
27.                    System.out.println("starting Time for"
28.                            + " task: "+ str +" = " +ft.format(d));  
29.                    //prints the starting time for every task
30.                }
31.                else
32.                {
33.                    Date d = new Date();
34.                    SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
35.                    System.out.println("Execution Time for task: "+
36.                            str +" = " +ft.format(d));  
37.                    // prints the completion time of every task
38.                }
39.                Thread.sleep(5000);
40.            }
41.            System.out.println(str+" done");
42.        }
43.      catch(InterruptedException e)
44.        {
45.            e.printStackTrace();
46.        }
47.    }
48. }
49.public class myTest
50.{
51.    // Maximum number of threads in thread pool
52.    static final int MAX_T = 3;            
53.    public static void main(String[] args)
54.    {
55.        // creates 3 tasks
56.        Runnable rn01 = new myTask("task 1");
57.        Runnable rn02 = new myTask("task 2");
58.        Runnable rn03 = new myTask("task 3");
59.        // Now, Step 2: Create an Executor Pool using Executors
60.        ExecutorService myPool = Executors.newFixedThreadPool(MAX_T); 
61.        // Step: 3 Pass all the requests (runnable tasks) to the Executor Pool
62.        myPool.execute(rn01);
63.        myPool.execute(rn02);
64.        myPool.execute(rn03);
65.        // Step: 4 Shutdown Pool
66.        myPool.shutdown();   
67.    }
68. }

Output:

Starting Time for task: task 1 = 01:12:46
Starting Time for task: task 3 = 01:12:46
Starting Time for task: task 2 = 01:12:46
Execution Time for task: task 1 = 01:12:46
Execution Time for task: task 2 = 01:12:46
Execution Time for task: task 3 = 01:12:46
Execution Time for task: task 1 = 01:12:46
Execution Time for task: task 2 = 01:12:46
Execution Time for task: task 3 = 01:12:46
Execution Time for task: task 1 = 01:12:46
Execution Time for task: task 2 = 01:12:46
Execution Time for task: task 3 = 01:12:46
task 1 done
task 2 done
task 3 done

One of the main benefits of using this approach of Java pool thread is when you have to process a large number of requests at a time, for instance, 100 requests but you obviously would not want to create 100 Threads, to reduce the JVM load and prevent it from running out of memory. You can use this approach to create a Thread Pool of 10 Threads and you can submit the 100 requests to this Thread Pool.

The Thread Pool will create a maximum of 10 threads to process 10 requests at a time.  Till then the remaining 90 requests will be placed in a queue. After completion of any single Thread, Java Thread Pool will internally allocate the 11th request to the available Thread. This will keep on going until all the remaining requests are executed.

Risks associated with using Java thread pool

Despite its efficiency, there are certain risks involved.

· Occurrence of a deadlock:

Just like a deadlock can occur in any multi-threaded program, a thread pool can also introduce a case of deadlock. It occurs when all executing threads are requested simultaneously and start waiting for the results from a blocked thread that is waiting in the queue due to the unavailability of threads for execution. It will create a sort of overlap, which becomes difficult to resolve.

There is no certain way of preventing deadlocks all the time. One technique is to avoid situations that may lead to deadlocks, like sharing resources or locking exclusively. If that is not possible or deadlocks are not very obvious, consider proper code hygiene. You must also monitor the thread pool and avoid any indefinite blocking.

· Thread leaking out of thread pool:

Thread Leakage could happen when a thread is removed from the thread pool to execute a request but is not returned to it when the request is executed. It can occur in various situations, for instance, if a thread throws an exception that the pool class does not catch, then the thread will just exit, reducing the number of threads in the pool by one. If this happens multiple times, the thread pool would eventually become empty and no threads would be available to execute the remaining requests.

You can prevent thread leakage by monitoring the pool and making sure the number of threads remains constant. If you find the number of threads is decreasing, identify the problem such as a wrong exception, and correct it as soon as possible.

· Resources thrashing:

If the size of a thread pool is significantly large then it will also take relatively more time in switching between threads. This will waste a lot of time and having more threads than a limited number of threads may cause starvation problems which can lead to resource thrashing or shortage of resources.

It can be easily avoided if you just, keep the number of threads in a pool at a limit and only create the number of threads that can get execute all the requests.

See Also: Writing a Parser In Java

Conclusion

We have discussed some basics of thread pool in Java, why do we need it, how to implement a thread pool in Java and what risks are involved. Java thread pools are an extremely useful tool for organizing server applications as it makes them stable and prevents any excessive use of resources. Despite the risks involved, it can improve your application to a great extent.

Author

Shaharyar Lalani is a developer with a strong interest in business analysis, project management, and UX design. He writes and teaches extensively on themes current in the world of web and app development, especially in Java technology.

Write A Comment