7. Tomcat线程池
7.1 Tomcat主要分为两个部分:
- 连接器部分:Connnector(NIO EndPoint),负责对外沟通
- 容器部分:Container,负责实现servlet规范,去运行一些servlet组件
7.2 Tomcat 在哪里用到了线程池呢:连接器部分中的worker
当浏览器向服务器发起一个请求的过程:首先到LimmitLatch
- LimitLatch 用来限流,可以控制最大连接个数,类似 J.U.C 中的 Semaphore 后面再讲
- Acceptor 死循环,校验有没有新的连接,只负责【接收新的 socket 连接】
- Poller 死循环,只负责监听 socket channel 是否有【可读的 I/O 事件】
- 一旦可读,封装一个任务对象(socketProcessor),提交给 Executor 线程池处理
- Executor 线程池中的工作线程最终负责【处理请求】
7.3 tomcat 线程池实现
Tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同
- 如果总线程数达到 maximumPoolSize,这时不会立刻抛 RejectedExecutionException 异常,而是再次尝试将任务放入队列,如果还失败,才抛出 RejectedExecutionException 异常
源码 tomcat-7.0.42:
// 覆盖父类的执行方法
public void execute(Runnable command, long timeout, TimeUnit unit) {
submittedCount.incrementAndGet();
try {
super.execute(command);
// 如果最大线程数到了,并且阻塞队列也满了,子类捉住异常并扩展
} catch (RejectedExecutionException rx) {
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue)super.getQueue();
try {
// 尝试任务重新进入阻塞队列
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.");
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
Thread.interrupted();
throw new RejectedExecutionException(x);
}
} else {
submittedCount.decrementAndGet();
throw rx;
}
}
}
TaskQueue中的任务插入阻塞队列方法:
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if ( parent.isShutdown() )
throw new RejectedExecutionException(
"Executor not running, can't force a command into the queue"
);
return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task
is rejected
}
7.4 Tomcat线程池相关配置
对应server.xml中相关配置:
7.4.1 Connector 配置
- acceptorThreadCount:不用改,一个线程做建立连接的活就够了,建立连接很多时候都是处于阻塞状态,没有连接就在那边等着,所以不需要那么多现成
- pollerThreadCount:不用改,采用多路复用的思想,一个线程就能监听多个channel
- minSpareThreads:核心线程数
- maxThreads:最大线程数
- executor:如果定义了,就覆盖上面的核心及最大线程数,使用自己配置的
7.5 executor配置:
7.5 tomcat线程池工作方式:
tomcat线程池的工作方式做了一定修改:
注意:
- 这里的核心任务<核心线程时,任务是先加入队列中,线程再去队列中获取任务
- 当提交任务数大于核心线程,小于最大线程时,任务先加到队列中
- 当任务大于最大线程数,才创建救急线程
8. Fork/Join线程池:
更多请看:https://blog.csdn.net/tyrroo/article/details/81390202
8.1 概念
Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型运算
所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解
Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率
Fork/Join 默认会创建与 cpu 核心数大小相同的线程池
8.2 使用
使用 提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下 面定义了一个对 1~n 之间的整数求和的任务
@Slf4j(topic = "TestFork")
public class TestFork {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
pool.invoke(new MyTask(5));
}
}
// 1~n之间整数的和
@Slf4j(topic = "myTask")
class MyTask extends RecursiveTask<Integer> {
private int n;
public MyTask(int n) {
this.n = n;
}
@Override
public String toString() {
return "{" + n + '}';
}
@SneakyThrows
@Override
protected Integer compute() {
// 如果 n 已经为 1,可以求得结果了
// 终止条件
if (n == 1) {
log.debug("join() {}", n);
return 1;
}
// 将任务进行拆分(fork)
MyTask t1 = new MyTask(n - 1);
t1.fork(); // 拆分,让一个线程去执行此任务
log.debug("fork() {} + {}", n, t1);
// 合并(join)结果
Thread.sleep(2000);
int result = n + t1.join(); // 获取任务结果
log.debug("join() {} + {} = {}", n, t1, result);
return result;
}
}
结果:
11:31:41.807 [ForkJoinPool-1-worker-1] DEBUG myTask - fork() 2 + {1}
11:31:41.807 [ForkJoinPool-1-worker-3] DEBUG myTask - fork() 5 + {4}
11:31:41.807 [ForkJoinPool-1-worker-5] DEBUG myTask - fork() 4 + {3}
11:31:41.807 [ForkJoinPool-1-worker-7] DEBUG myTask - fork() 3 + {2}
11:31:43.831 [ForkJoinPool-1-worker-7] DEBUG myTask - join() 1
11:31:43.833 [ForkJoinPool-1-worker-1] DEBUG myTask - join() 2 + {1} = 3
11:31:43.834 [ForkJoinPool-1-worker-7] DEBUG myTask - join() 3 + {2} = 6
11:31:43.834 [ForkJoinPool-1-worker-5] DEBUG myTask - join() 4 + {3} = 10
11:31:43.835 [ForkJoinPool-1-worker-3] DEBUG myTask - join() 5 + {4} = 15
用图表示:
8.3 改进
从上述图中可以看出,任务1等待任务2的结果,任务2等待任务3的结果,所以还是一个串行化的操作,所以我们需要拆分他们的依赖,改为尽量并行执行:
拆为2个任务:修改task类:
@Slf4j(topic = "myTask2")
class MyTask2 extends RecursiveTask<Integer> {
int begin;
int end;
public MyTask2(int begin, int end) {
this.begin = begin;
new ReentrantLock();
this.end = end;
}
@Override
public String toString() {
return "{" + begin + "," + end + '}';
}
@Override
protected Integer compute() {
// 5, 5
if (begin == end) {
log.debug("join() {}", begin);
return begin;
}
// 4, 5
if (end - begin == 1) {
log.debug("join() {} + {} = {}", begin, end, end + begin);
return end + begin;
}
// 1 5
int mid = (end + begin) / 2; // 3
MyTask2 t1 = new MyTask2(begin, mid); // 1,3
t1.fork();
MyTask2 t2 = new MyTask2(mid + 1, end); // 4,5
t2.fork();
log.debug("fork() {} + {} = ?", t1, t2);
int result = t1.join() + t2.join();
log.debug("join() {} + {} = {}", t1, t2, result);
return result;
}
}
结果:
11:45:21.203 [ForkJoinPool-1-worker-5] DEBUG myTask2 - fork() {1,2} + {3,3} = ?
11:45:21.203 [ForkJoinPool-1-worker-1] DEBUG myTask2 - join() 1 + 2 = 3
11:45:21.203 [ForkJoinPool-1-worker-3] DEBUG myTask2 - fork() {1,3} + {4,5} = ?
11:45:21.203 [ForkJoinPool-1-worker-7] DEBUG myTask2 - join() 4 + 5 = 9
11:45:21.210 [ForkJoinPool-1-worker-1] DEBUG myTask2 - join() 3
11:45:21.210 [ForkJoinPool-1-worker-5] DEBUG myTask2 - join() {1,2} + {3,3} = 6
11:45:21.210 [ForkJoinPool-1-worker-3] DEBUG myTask2 - join() {1,3} + {4,5} = 15
图片展示: