executor
Executor is the engine, user requests are processed by executor, perception tells us this should be a multi-threaded, thread pool container, let's first look at the relationship between classes;
public interface Executor extends java.util.concurrent.Executor, Lifecycle { public String getName(); @Deprecated void execute(Runnable command, long timeout, TimeUnit unit); }
Executor is an interface and inherits the executor interface under jdk's juc package
StandardThreadExecutor property
standardThreadExecutor is an implementation class, as shown in the figure, which also inherits lifecycle-related interfaces and is managed by tomcat.
public class StandardThreadExecutor extends LifecycleMBeanBase implements Executor, ResizableExecutor { protected static final StringManager sm = StringManager.getManager(StandardThreadExecutor.class); protected int threadPriority = Thread.NORM_PRIORITY; //Default daemon thread, main line will exit automatically after exit, there will be no residual protected boolean daemon = true; //Prefix to Thread Name protected String namePrefix = "tomcat-exec-"; //Default maximum thread 200 protected int maxThreads = 200; //(int) The minimum number of threads (idle and active) is always active, defaulting to 25 protected int minSpareThreads = 25; //Maximum active time 60 seconds protected int maxIdleTime = 60000; //Note that ThreadPoolExecutor here is a thread pool implemented by tomcat, not under the juc package protected ThreadPoolExecutor executor = null; //Thread Name protected String name; protected boolean prestartminSpareThreads = false; //Maximum Queue Size protected int maxQueueSize = Integer.MAX_VALUE; //(long)If ThreadLocalLeakPreventionListener is configured, it will notify this executor about the stopped context. When the context is stopped, threads in the pool will be updated. To avoid updating all threads simultaneously, this option sets a delay between renewals of any two threads. The value is MS and the default value is 1000ms. If the value is negative, threads will not be renewed.Lifecycle Template Method protected long threadRenewalDelay = org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY; // Task Queue private TaskQueue taskqueue = null;
Let's look at how this component starts, the startInternal() method of the lifecycle
@Override protected void startInternal() throws LifecycleException { //Self-implemented task queue, described later taskqueue = new TaskQueue(maxQueueSize); TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority()); //25 core threads, up to 200 executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf); executor.setThreadRenewalDelay(threadRenewalDelay); if (prestartminSpareThreads) { executor.prestartAllCoreThreads(); } taskqueue.setParent(executor); setState(LifecycleState.STARTING); }
stopInternal method
@Override protected void stopInternal() throws LifecycleException { setState(LifecycleState.STOPPING); if (executor != null) { executor.shutdownNow(); } executor = null; taskqueue = null; }
- Core executor method
@Override public void execute(Runnable command, long timeout, TimeUnit unit) { if (executor != null) { executor.execute(command,timeout,unit); } else { throw new IllegalStateException(sm.getString("standardThreadExecutor.notStarted")); } } @Override public void execute(Runnable command) { if (executor != null) { try { executor.execute(command); } catch (RejectedExecutionException rx) { //there could have been contention around the queue if (!((TaskQueue) executor.getQueue()).force(command)) { throw new RejectedExecutionException(sm.getString("standardThreadExecutor.queueFull")); } } } else { throw new IllegalStateException(sm.getString("standardThreadExecutor.notStarted")); } }
Supplement TaskQueue
We know that the work queue is guaranteed by TaskQueue, which inherits from LinkedBlockingQueue, a blocked list queue. Look at the source code.
/** * As task queue specifically designed to run with a thread pool executor. The * task queue is optimised to properly utilize threads within a thread pool * executor. If you use a normal queue, the executor will spawn threads when * there are idle threads and you wont be able to force items onto the queue * itself. */ public class TaskQueue extends LinkedBlockingQueue<Runnable> { private static final long serialVersionUID = 1L; protected static final StringManager sm = StringManager .getManager("org.apache.tomcat.util.threads.res"); private static final int DEFAULT_FORCED_REMAINING_CAPACITY = -1; private transient volatile ThreadPoolExecutor parent = null; // No need to be volatile. This is written and read in a single thread // (when stopping a context and firing the listeners) private int forcedRemainingCapacity = -1; public TaskQueue() { super(); } public TaskQueue(int capacity) { super(capacity); } public TaskQueue(Collection<? extends Runnable> c) { super(c); } public void setParent(ThreadPoolExecutor tp) { parent = tp; } public boolean force(Runnable o) { if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); return super.offer(o); //forces the item onto the queue, to be used if the task is rejected } public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected } @Override public boolean offer(Runnable o) { //we can't do any checks if (parent==null) return super.offer(o); //we are maxed out on threads, simply queue the object if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o); //we have idle threads, just add it to the queue if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o); //if we have less threads than maximum force creation of a new thread if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false; //if we reached here, we need to add it to the queue return super.offer(o); } @Override public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException { Runnable runnable = super.poll(timeout, unit); if (runnable == null && parent != null) { // the poll timed out, it gives an opportunity to stop the current // thread if needed to avoid memory leaks. parent.stopCurrentThreadIfNeeded(); } return runnable; } @Override public Runnable take() throws InterruptedException { if (parent != null && parent.currentThreadShouldBeStopped()) { return poll(parent.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS); // yes, this may return null (in case of timeout) which normally // does not occur with take() // but the ThreadPoolExecutor implementation allows this } return super.take(); } @Override public int remainingCapacity() { if (forcedRemainingCapacity > DEFAULT_FORCED_REMAINING_CAPACITY) { // ThreadPoolExecutor.setCorePoolSize checks that // remainingCapacity==0 to allow to interrupt idle threads // I don't see why, but this hack allows to conform to this // "requirement" return forcedRemainingCapacity; } return super.remainingCapacity(); } public void setForcedRemainingCapacity(int forcedRemainingCapacity) { this.forcedRemainingCapacity = forcedRemainingCapacity; } void resetForcedRemainingCapacity() { this.forcedRemainingCapacity = DEFAULT_FORCED_REMAINING_CAPACITY; } }
We know that TaskQueue is an infinite queue, but it overrides the offer method to return false when its thread pool size is less than maximumPoolSize, which overrides the logic of a full queue to some extent.
Fixed a bug where maxThreads failed when using the LinkedBlockingQueue default capacity of Integer.MAX_VALUE (non-core threads are opened only when the queue is full, where Integer.MAX_VALUE is never reached, so the maximum number of threads can never be reached).
This allows you to continue growing threads to maxThreads and then continue queuing after that.
TaskQueue This task queue is specifically designed for thread pools. Optimize the task queue to appropriately utilize threads within the thread pool executor.
If you use a normal queue, when an idle thread executor spawns a thread and you cannot force a task to be added to the queue.
Why not use ThreadPoolExecutor directly? Have you considered a question here?
Why does Tomcat construct a StandardThreadExecutor by itself instead of using ThreadPoolExecutor directly?
From the code above, you will find that using executor here is only using the two main methods of execute, and it wants the calling layer to block out the other methods of ThreadPoolExecutor:
- It embodies the principle of minimum knowledge: Talk only to your close friends. That is, the client should interact with as few people as possible
- The design pattern it represents: Facade pattern, which provides a unified interface for accessing a set of interfaces in a subsystem, making it easier for the subsystem to use