The implementation principle of Tomcat correcting the JDK native thread pool bug

The implementation principle of Tomcat correcting the JDK native thread pool bug

To improve processing power and concurrency, Web containers generally put the task of processing requests into the thread pool. The native thread pool of JDK is inherently suitable for CPU-intensive tasks, so Tomcat modified it.

Tomcat thread pool principle

In fact, the parameters of ThreadPoolExecutor mainly have the following key points:

Limit the number of threads

Limiting queue length

Tomcat needs to limit these two resources, otherwise the CPU and memory may be exhausted under high concurrency.
Therefore, Tomcat's thread pool passes parameters:

// Customized task queue taskqueue = new TaskQueue(maxQueueSize);

// Customized thread factory TaskThreadFactory tf = new TaskThreadFactory(namePrefix,
							                 daemon,
							                 getThreadPriority()
);

// Custom thread pool executor = new ThreadPoolExecutor(getMinSpareThreads(),
								  getMaxThreads(),
				 			      maxIdleTime, 
				 			      TimeUnit.MILLISECONDS,
				 			      taskqueue,
				 			      tf);

Tomcat also has a limit on the number of threads, set:

  • Number of core threads (minSpareThreads)
  • Maximum number of thread pools (maxThreads)

Tomcat thread pool also has its own special task processing flow, which implements its own special task processing logic by rewriting the execute method:

  1. When there are corePoolSize tasks, a new thread is created for each task.
  2. If there are more tasks, put them into the task queue and let all threads grab them. If the queue is full, create a temporary thread
  3. When the total number of threads reaches maximumPoolSize, continue to try to put tasks into the task queue
  4. If the buffer queue is also full, the insertion fails and the rejection strategy is executed

The difference between Tomcat and JDK thread pool is in step 3. When the total number of threads reaches the maximum number, Tomcat does not immediately execute the rejection strategy, but tries to add tasks to the task queue. If the addition fails, it executes the rejection strategy again .

How is it achieved specifically?

public void execute(Runnable command, long timeout, TimeUnit unit) {
    submittedCount.incrementAndGet();
    try {
        //Call the execute function of JDK native thread pool to execute the task super.execute(command);
    } catch (RejectedExecutionException rx) {
       // When the total number of threads reaches maximumPoolSize, the JDK native thread pool will execute the default rejection policy if (super.getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue)super.getQueue();
            try {
                // Continue trying to put the task into the task queue if (!queue.force(command, timeout, unit)) {
                    submittedCount.decrementAndGet();
                    // If the buffer queue is still full, the insertion fails and the rejection strategy is executed.
                    throw new RejectedExecutionException("...");
                }
            } 
        }
    }
}

Customizing the task queue

The first line of the execute method of the Tomcat thread pool:

submittedCount.incrementAndGet();

When a task fails and an exception is thrown, the counter is reduced by one:

submittedCount.decrementAndGet();

The Tomcat thread pool uses the submittedCount variable to maintain the number of tasks that have been submitted to the thread pool but have not been executed.

Why maintain such a variable?

Tomcat's task queue TaskQueue extends JDK's LinkedBlockingQueue. Tomcat gives it a capacity and passes it to the constructor of the parent class LinkedBlockingQueue.

public class TaskQueue extends LinkedBlockingQueue<Runnable> {

  public TaskQueue(int capacity) {
      super(capacity);
  }
  ...
}

The capacity parameter is set by Tomcat's maxQueueSize parameter, but the default value of maxQueueSize is Integer.MAX_VALUE : after the current number of threads reaches the number of core threads, if there is another task, the thread pool will add the task to the task queue and it will always succeed, so there will never be a chance to create a new thread.

To solve this problem, TaskQueue rewrites LinkedBlockingQueue#offer and returns false at the right time, indicating that the task addition failed. At this time, the thread pool will create a new thread.

What is the right time?

public class TaskQueue extends LinkedBlockingQueue<Runnable> {

  ...
   @Override
  // When the thread pool calls the task queue method, the current number of threads > the number of core threads public boolean offer(Runnable o) {

      // If the number of threads has reached max, new threads cannot be created and can only be put into the task queue if (parent.getPoolSize() == parent.getMaximumPoolSize()) 
          return super.offer(o);
          
      // So far, it indicates that the max number of threads > the current number of threads > the core number of threads // indicates that new threads can be created:
      
      // 1. If the number of submitted tasks < the current number of threads // it means there are still idle threads and no new threads need to be created if (parent.getSubmittedCount()<=(parent.getPoolSize())) 
          return super.offer(o);
          
      // 2. If the number of submitted tasks > the current number of threads // There are not enough threads, return false to create a new thread if (parent.getPoolSize()<parent.getMaximumPoolSize()) 
          return false;
          
      // By default, always put the task into the task queue return super.offer(o);
  }
  
}

Therefore, Tomcat maintains the number of submitted tasks in order to allow the thread pool to have the opportunity to create new threads when the task queue length is infinite.

This is the end of this article about how Tomcat fixes the JDK native thread pool bug. For more information about Tomcat JDK native thread pool, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Tomcat uses thread pool to handle remote concurrent requests
  • Detailed explanation of the number of connections and thread pool based on Tomcat
  • Analysis of the use cases of JDK thread pool and Spring thread pool
  • Detailed explanation of jdk's own thread pool instance

<<:  Six ways to increase your website speed

>>:  Introduction to the usage of props in Vue

Recommend

How to use shtml include

By applying it, some public areas of the website c...

Summary of the application of transition components in Vue projects

​Transtion in vue is an animation transition enca...

Difference between var and let in JavaScript

Table of contents 1. Scopes are expressed in diff...

A detailed introduction to the basics of Linux scripting

Table of contents 1. Script vim environment 2. Ho...

About the usage and precautions of promise in javascript (recommended)

1. Promise description Promise is a standard buil...

Implementing a web calculator based on JavaScript

This article shares the specific code of JavaScri...

Fabric.js implements DIY postcard function

This article shares the specific code of fabricjs...

Ubuntu 15.04 opens mysql remote port 3306

Ubuntu 15.04 opens MySQL remote port 3306. All th...

Solution to the problem of var in for loop

Preface var is a way to declare variables in ES5....

Use CSS to create 3D photo wall effect

Use CSS to create a 3D photo wall. The specific c...

Detailed explanation of Vue's seven value transfer methods

1. From father to son Define the props field in t...

Nodejs converts JSON string into JSON object error solution

How to convert a JSON string into a JSON object? ...

Realize three-level linkage of year, month and day based on JavaScript

This article shares the specific code for JavaScr...