文章目录
- 前言
- 正文
-
- 一、执行线程的基本流程
-
- [1.1 JUC中的线程池执行线程](#1.1 JUC中的线程池执行线程)
- [1.2 Tomcat 中线程池执行线程](#1.2 Tomcat 中线程池执行线程)
- 二、被改造的阻塞队列
-
- [2.1 TaskQueue的 offer(...)](#2.1 TaskQueue的 offer(...))
- [2.2 TaskQueue的 force(...)](#2.2 TaskQueue的 force(...))
- 三、总结
前言
Tomcat 线程池,是依据 JUC 中的线程池 ThreadPoolExecutor
重新自定义实现的。
其执行线程的代码逻辑,和JUC 中是相同的。主要区别在于,Tomcat中对 阻塞队列进行了改造。
本文主要研究 Tomcat 的线程池是如何执行线程的,即线程池的工作原理。
同系列文章:Tomcat线程池原理(上篇:初始化原理)
正文
一、执行线程的基本流程
Tomcat 中执行线程的基本流程,和JUC中是一致的。
以下贴出两种执行方法。
1.1 JUC中的线程池执行线程
在ThreadPoolExecutor
中,执行线程的方法定义如下:
java
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
1.2 Tomcat 中线程池执行线程
java
@Override
public void execute(Runnable command) {
execute(command,0,TimeUnit.MILLISECONDS);
}
重载方法定义如下:
这个方法被Deprecated
标注,源码中注释中描述说是,Tomcat10中会被删除,估计是一种优化。本文不做研究。
java
@Deprecated
public void execute(Runnable command, long timeout, TimeUnit unit) {
// 提交的任务数量+1
submittedCount.incrementAndGet();
try {
// 执行线程任务(这个方法中的代码和JUC中执行线程的代码一致,差别在于,阻塞队列使用了Tomcat自定义的队列)
executeInternal(command);
} catch (RejectedExecutionException rx) {
// 被拒绝后,尝试将线程放入队列
// 如果当前队列是Tomcat自定义的队列TaskQueue,尝试将任务放入队列
if (getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue) getQueue();
try {
// 强制将线程任务放入阻塞队列
if (!queue.force(command, timeout, unit)) {
// 放入队列失败,提交的任务数-1
submittedCount.decrementAndGet();
// 抛出异常,线程池队列已满
throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
}
} catch (InterruptedException x) {
// 中断时,提交的任务数-1
submittedCount.decrementAndGet();
// 抛出异常
throw new RejectedExecutionException(x);
}
} else {
// 当前指定的队列不是TaskQueue,提交的任务数-1,并抛出异常
submittedCount.decrementAndGet();
throw rx;
}
}
}
另外,executeInternal
的内容如下(和JUC中基本一致):区别在于阻塞队列换成了TaskQueue
java
private void executeInternal(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) {
return;
}
c = ctl.get();
}
// 这里的workQueue,实际已经换成了TaskQueue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) {
reject(command);
} else if (workerCountOf(recheck) == 0) {
addWorker(null, false);
}
}
else if (!addWorker(command, false)) {
reject(command);
}
}
二、被改造的阻塞队列
从第一小节中,看到的东西并不多。因为,Tomcat线程池的改造重心,在于阻塞队列,也就是 TaskQueue
。
2.1 TaskQueue的 offer(...)
java
@Override
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) {
return super.offer(o);
}
// 若是达到最大线程数,直接进队列
if (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) {
return super.offer(o);
}
// 已提交任务数小于等于当前线程数
// 对应的场景是:当前活跃线程为10个,但是只有8个任务在执行,于是,直接进队列
if (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) {
return super.offer(o);
}
// 当前线程数小于最大线程数,此次拒绝进入队列
if (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) {
return false;
}
//if we reached here, we need to add it to the queue
return super.offer(o);
}
2.2 TaskQueue的 force(...)
其本质是,直接入队列。
java
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
}
三、总结
根据源码中对于TaskQueue的改造,可以观察出,Tomcat线程池的主要特点如下:
- 当前线程数小于corePoolSize,则去创建工作线程;
- 当前线程数大于corePoolSize,但小于maximumPoolSize,则去创建工作线程;
- 当前线程数大于maximumPoolSize,则将任务放入到阻塞队列中,当阻塞队列满了之后,则调用拒绝策略丢弃任务;
- 任务执行失败时不会直接抛出错误,而是装回队列里再次尝试执行;
- 当线程池没有达到最大执行线程的时候,会优先开线程再使用任务队列;
总结之后就是,Tomcat 为了更适配 IO 密集型任务,改造了阻塞队列。与JUC相比,会先去创建线程执行任务,创建的线程数达到最大线程数时,再放入队列等待空闲线程的出现。