Java thread pool overview

Server programs, such as databases and Web servers, repeatedly execute requests from multiple clients designed to handle a large number of short tasks. One way to build a server application is to create a new thread each time a request arrives and service the new request in the newly created thread. Although this method seems simple to implement, it also has obvious disadvantages. The server that creates a new thread for each request will spend more time and consume more system resources to create and destroy threads than the actual request.

Because active threads will consume system resources, and creating too many threaded JVM s will lead to insufficient system memory. This requires limiting the number of threads being created.

Java Thread pool in

Based on the above concepts, it is natural for us to consider using thread pool. The thread pool reuses previously created threads to execute the current task, and provides a solution to the problems of thread cycle overhead and resource jitter. Since the thread already exists when the request arrives, the delay introduced by thread creation is eliminated, making the application more responsive.

  • Java provides an Executor framework centered on the Executor interface. Its sub interface ExecutorService and the class that implements the two interfaces ThreadPoolExecutor. By using an actuator, you simply implement Runnable objects and send them to the actuator for execution.
  • They allow you to take advantage of threads, but focus on the tasks you want them to perform, rather than threading mechanisms.
  • To use the thread pool, we first create an ExecutorService object and pass a set of tasks to it. The ThreadPoolExecutor class allows you to set the core and maximum pool size. Runnable objects run by a particular thread are executed sequentially.

Several methods of thread pool in Executor framework

Method                         Description
newFixedThreadPool(int)           Creates a fixed size thread pool.
newCachedThreadPool()             Creates a thread pool that creates new 
                                  threads as needed, but will reuse previously 
                                  constructed threads when they are available
newSingleThreadExecutor()         Creates a single thread. 

In the case of fixed thread pool, if the executor is currently running all threads, the suspended tasks are put into the queue and executed when the threads are idle.

Thread pool example

In the following, we will look at a basic example of thread pool executor - FixedThreadPool.

  1. First, create a task to be executed (a class that implements the Runnable interface)
  2. Creating a thread pool using Executors
  3. Pass task to thread pool
  4. Close thread pool
// Java program to illustrate
// ThreadPool
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// Task class to be executed (Step 1)
class Task implements Runnable
{
  private String name;

  public Task(String s)
  {
    name = s;
  }

  // Prints task name and sleeps for 1s
  // This Whole process is repeated 5 times
  public void run()
  {
    try
    {
      for (int i = 0; i<=5; i++)
      {
        if (i==0)
        {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("Initialization Time for"
              + " task name - "+ name +" = " +ft.format(d));
          //prints the initialization time for every task
        }
        else
        {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("Executing Time for task name - "+
              name +" = " +ft.format(d));
          // prints the execution time for every task
        }
        Thread.sleep(1000);
      }
      System.out.println(name+" complete");
    }

    catch(InterruptedException e)
    {
      e.printStackTrace();
    }
  }
}
public class Test
{
  // Maximum number of threads in thread pool
  static final int MAX_T = 3;      

  public static void main(String[] args)
  {
    // creates five tasks
    Runnable r1 = new Task("task 1");
    Runnable r2 = new Task("task 2");
    Runnable r3 = new Task("task 3");
    Runnable r4 = new Task("task 4");
    Runnable r5 = new Task("task 5");  

    // creates a thread pool with MAX_T no. of
    // threads as the fixed pool size(Step 2)
    ExecutorService pool = Executors.newFixedThreadPool(MAX_T);

    // passes the Task objects to the pool to execute (Step 3)
    pool.execute(r1);
    pool.execute(r2);
    pool.execute(r3);
    pool.execute(r4);
    pool.execute(r5);

    // pool shutdown ( Step 4)
    pool.shutdown();  
  }
}

output

Output:
Initialization Time for task name - task 2 = 02:32:56
Initialization Time for task name - task 1 = 02:32:56
Initialization Time for task name - task 3 = 02:32:56
Executing Time for task name - task 1 = 02:32:57
Executing Time for task name - task 2 = 02:32:57
Executing Time for task name - task 3 = 02:32:57
Executing Time for task name - task 1 = 02:32:58
Executing Time for task name - task 2 = 02:32:58
Executing Time for task name - task 3 = 02:32:58
Executing Time for task name - task 1 = 02:32:59
Executing Time for task name - task 2 = 02:32:59
Executing Time for task name - task 3 = 02:32:59
Executing Time for task name - task 1 = 02:33:00
Executing Time for task name - task 3 = 02:33:00
Executing Time for task name - task 2 = 02:33:00
Executing Time for task name - task 2 = 02:33:01
Executing Time for task name - task 1 = 02:33:01
Executing Time for task name - task 3 = 02:33:01
task 2 complete
task 1 complete
task 3 complete
Initialization Time for task name - task 5 = 02:33:02
Initialization Time for task name - task 4 = 02:33:02
Executing Time for task name - task 4 = 02:33:03
Executing Time for task name - task 5 = 02:33:03
Executing Time for task name - task 5 = 02:33:04
Executing Time for task name - task 4 = 02:33:04
Executing Time for task name - task 4 = 02:33:05
Executing Time for task name - task 5 = 02:33:05
Executing Time for task name - task 5 = 02:33:06
Executing Time for task name - task 4 = 02:33:06
Executing Time for task name - task 5 = 02:33:07
Executing Time for task name - task 4 = 02:33:07
task 5 complete
task 4 complete

It can be seen from the execution of the program that task 4 or task 5 will be executed only when the threads in the pool are idle. Until then, additional tasks are placed in a queue.

Problems with using thread pool

  1. Deadlock: Although deadlock may occur in any multithreaded program, thread pool introduces another deadlock situation. In this case, due to the unavailability of threads, all executing threads are waiting for the result of blocking threads waiting in the queue.
  2. Thread leakage: thread leakage occurs if a thread is deleted from the pool to execute a task but is not returned to it when the task is completed. For example, if a thread throws an exception and the pool class does not catch the exception, the thread simply exits, reducing the size of the thread pool by one. In this case, the pool will be empty many times, and if there are no other threads to execute, it will eventually become empty.
  3. Resource jitter: if the thread pool size is very large, it wastes time in context switching between threads. As explained, having more threads than the optimal number can lead to hunger problems that cause resource jitters.

Points for attention when using thread pool

  1. Do not queue tasks that are waiting for the results of other tasks at the same time. This may result in a deadlock condition as described above.
  2. Be careful when using threads for long-term operations. This may cause the thread to wait forever and eventually lead to resource leakage.
  3. The thread pool must end explicitly at the end. If you don't, the program will continue to execute and never end. Call shutdown() on the pool to end the program execution. If you try to send another task to the actuator after closing, it will throw RejectedExecutionException.
  4. You need to understand the task of effectively adjusting the thread pool. If tasks vary widely, it makes sense to use different thread pools for different types of tasks in order to adjust them correctly.
  5. You can limit the maximum number of threads that can run in the JVM, thereby reducing the chance of the JVM running out of memory.
  6. If you need to implement a loop to create a new thread for processing, using ThreadPool will help you process faster, because ThreadPool will not create a new thread after reaching the maximum limit.
  7. After Thread Processing is completed, ThreadPool can use the same Thread as another process (this can save time and resources for creating another Thread.)

How to set thread pool

The optimal size of the thread pool depends on the number of processors available and the nature of the task. On an n processor system that contains only queues of compute type processes, a maximum thread pool size of N or N+1 will achieve maximum efficiency. However, tasks may wait for I/O. in this case, we will consider the ratio of request waiting time (W) to service time (S), and set the maximum pool size to N*(1+ W/S) to achieve maximum efficiency.

Thread pools are a useful tool for organizing server applications. It is very simple in concept, but several problems need to be paid attention to when implementing and using it, such as deadlock and resource jitter.

This article is an original article from big data to artificial intelligence blogger "xiaozhch5", which follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement for reprint.

Original link: https://lrting.top/backend/3740/

Added by Alt_F4 on Thu, 10 Feb 2022 12:15:00 +0200