JavaEdge: JavaEdge: JavaEdge: JavaEdge: JavaEdge Java-Interview-Tutorial

In order to improve processing power and concurrency, Web containers typically put the task of processing requests into a thread pool, and Tomcat adapted the JDK’s native thread pool for CPU-intensive tasks.

Principles of Tomcat thread pool

ThreadPoolExecutor parameters have the following key points:

  • Limit the number of threads

  • Limit queue length

Tomcat needs to limit both of these resources. Otherwise, the CPU and memory may be exhausted under high concurrency. So Tomcat’s thread pool passes parameters:

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

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

// Customize the thread pool
executor = new ThreadPoolExecutor(getMinSpareThreads(),
								  getMaxThreads(),
				 			      maxIdleTime, 
				 			      TimeUnit.MILLISECONDS,
				 			      taskqueue,
				 			      tf);
Copy the code

Tomcat also has a limit on the number of threads.

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

The Tomcat thread pool also has its own characteristic task processing flow, and the execute method is overridden to achieve its characteristic task processing logic:

  1. Before corePoolSize tasks, a task to create a new thread
  2. Any more tasks are put into a task queue for all threads to grab. If the queue is full, a temporary thread is created
  3. When the total number of threads reaches maximumPoolSize, the attempt to put the task on the task queue continues
  4. If the buffer queue is also full, the insert fails and the reject policy is executed

The difference from JDK thread pool is in step3, Tomcat does not immediately execute the reject policy when the number of threads reaches the maximum. Instead, Tomcat tries to add tasks to the task queue again, and then executes the reject policy after the addition fails.

How does it work?

public void execute(Runnable command, long timeout, TimeUnit unit) {
    submittedCount.incrementAndGet();
    try {
        // Call execute in the 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 implements a default reject 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 insert fails and the reject policy is executed.
                    throw new RejectedExecutionException("...");
                }
            } 
        }
    }
}
Copy the code

Custom task queue

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

submittedCount.incrementAndGet();
Copy the code

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

submittedCount.decrementAndGet();
Copy the code

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

Why maintain such a variable?

Tomcat’s TaskQueue extends the JDK’s LinkedBlockingQueue by giving it a capacity that is passed to the constructor of its parent LinkedBlockingQueue.

public class TaskQueue extends LinkedBlockingQueue<Runnable> {

  public TaskQueue(int capacity) {
      super(capacity); }... }Copy the code

The capacity parameter is set using Tomcat’s maxQueueSize parameter, but the default value for maxQueueSize is integer. MAX_VALUE: When the current number of threads reaches the core number, the thread pool will add the task to the task queue, and it will always succeed, and there will never be a chance to create a new thread.

To solve this problem, TaskQueue overwrites LinkedBlockingQueue#offer to return false when appropriate, indicating that the task failed to be added, at which point the thread pool creates a new thread.

What do you mean the right time?

public class TaskQueue extends LinkedBlockingQueue<Runnable> {...@Override
  // When the thread pool calls the task queue method, the current thread count is greater than the core thread count
  public boolean offer(Runnable o) {

      // If the number of threads reaches Max, no new threads can be created
      if (parent.getPoolSize() == parent.getMaximumPoolSize()) 
          return super.offer(o);
          
      Max thread count > current thread count > core thread count
      New threads can be created:
      
      // 1. If the number of submitted tasks is less than the number of current threads
      // indicates that there are still free threads, no need to create a new thread
      if (parent.getSubmittedCount()<=(parent.getPoolSize())) 
          return super.offer(o);
          
      // 2. If number of submitted tasks > number of current threads
      // There are not enough threads, return false to create a new thread
      if (parent.getPoolSize()<parent.getMaximumPoolSize()) 
          return false;
          
      // By default, tasks are always put into a task queue
      return super.offer(o); }}Copy the code

So Tomcat maintains the number of committed tasks in order to give the thread pool the opportunity to create new threads when the task queue length is infinite.