executor
executor 是引擎,用户请求是经过executor进行处理的,知觉告诉我们这应该是一个多线程,线程池的容器,我们先从类的关系看下;
public interface Executor extends java.util.concurrent.Executor, Lifecycle {
public String getName();
@Deprecated
void execute(Runnable command, long timeout, TimeUnit unit);
}
executor是个接口,并继承jdk的juc包下面的executor接口
StandardThreadExecutor属性
standardThreadExecutor是实现类,如图它也继承了生命周期相关的接口,受tomcat的生命周期管理。
public class StandardThreadExecutor extends LifecycleMBeanBase
implements Executor, ResizableExecutor {
protected static final StringManager sm = StringManager.getManager(StandardThreadExecutor.class);
protected int threadPriority = Thread.NORM_PRIORITY;
//默认守护线程,主线退出后,会自动退出,不会有残余
protected boolean daemon = true;
//线程名字的前缀
protected String namePrefix = "tomcat-exec-";
//默认最大线程200
protected int maxThreads = 200;
//(int)最小线程数(空闲和活动)始终保持活动状态,默认为 25
protected int minSpareThreads = 25;
//最长活跃时间 60秒
protected int maxIdleTime = 60000;
//注意这里的ThreadPoolExecutor 是tomcat实现的线程池,不是juc包下的
protected ThreadPoolExecutor executor = null;
//线程名称
protected String name;
protected boolean prestartminSpareThreads = false;
//最大的队列大小
protected int maxQueueSize = Integer.MAX_VALUE;
//(long)如果配置了ThreadLocalLeakPreventionListener,它将通知此执行程序有关已停止的上下文。上下文停止后,池中的线程将被更新。为避免同时更新所有线程,此选项在任意2个线程的续订之间设置延迟。该值以ms为单位,默认值为1000ms。如果值为负,则不会续订线程。 ¶ Lifecycle模板方法
protected long threadRenewalDelay =
org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;
// 任务队列
private TaskQueue taskqueue = null;
我们看下这个组件启动的方法,生命周期的startInternal()方法
@Override
protected void startInternal() throws LifecycleException {
//自己实现的任务队列,后面讲下
taskqueue = new TaskQueue(maxQueueSize);
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
//核心线程25个,最大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方法
@Override
protected void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
if (executor != null) {
executor.shutdownNow();
}
executor = null;
taskqueue = null;
}
- 核心executor方法
@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"));
}
}
补充TaskQueue
我们知道工作队列是有TaskQueue保障的,它继承自LinkedBlockingQueue(一个阻塞的链表队列),来看下源代码吧。
/**
* 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;
}
}
让我们回忆下jdk线程池的增长过程:
- 1.优先创建线程数量至核心线程数。当线程池中的数量小于coreSize,即使有核心线程空闲,也会创建核心线程来执行;
- 2.达到核心线程数后。向队列扔任务,如果队列未满,添加任务,如果队列满了,创建非核心线程执行任务;如果队列和非核心线程满了,触发拒绝策略;
TaskQueue是无界队列,按照上述所讲任务永远不会满,一直添加任务,不会启动非核心线程。但是,其重写offer方法,当其线程池中线程大小小于maximumPoolSize的时候,返回false,如果返回fase,那么会创建线程执行任务,从何达到,即使无界队列也能突破核心线程的限制,增长线程到maxThreads,超过之后。
一句话,为了兼容所有任务,采用无界队列;传统的jdk无界队列,非核心线程永远利用不到,但是taskQueue可以做到采用无界队列,还能让非核心线程也能启动。所以TaskQueue是专门为线程池而设计的。
为什么不是直接使用ThreadPoolExecutor 这里你是否考虑过一个问题
为什么Tomcat会自己构造一个StandardThreadExecutor而不是直接使用ThreadPoolExecutor?
从上面的代码,你会发现这里只使用了execute的两个主要方法,它希望让调用层屏蔽掉ThreadPoolExecutor的其它方法:
- 它体现的原则:最少知识原则: 只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少
- 它体现的设计模式:结构型 - 外观(Facade) 外观模式(Facade pattern),它提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用