一、什么是线程池?
具体可参考《线程池学习笔记》
二、举个栗子(用比喻的方式解释线程池工作原理)
1、相关名词
线程池 | 开发部门 |
---|---|
核心线程 | 正式员工(长期存在) |
非核心线程 | 非正式员工(如外包员工,需要才用到) |
执行任务 | 开发需求 |
阻塞队列 | 开发需求TODO List(排不下期,需要排队等有人力开发) |
空闲时间(非核心线程存活时间) | 非正式员工待命时间(没需求做之后仍空闲停留的时间) |
拒绝策略 | 需求饱和后,接下来的需求需要评估看看是不做了还是谁提谁做等 |
2、开始讲故事
线程池:
- 有一天,老板想要开发一个开发系统A,但是自己团队并没有开发人员(线程)
- 所以老板去58同城找了开发A(线程A)完成任务
- 第二天老板又想开发一个开发系统B,又只能去58同城找了**开发B(线程B)完成任务
- …
- 久而久之,老板发现这样很麻烦,每次都要重复去找人开发,所以老板自己招了三个程序员A、B、C(线程A、B、C),成立了一个开发团队(线程池),后面的需求就可以内部分配完成了
核心线程:
- 也就是正式员工,长期稳定做需求的
阻塞队列:
- 当产品下发了10个需求,但是开发只有3个,一人领走一个需求,那么剩下的7个需求就先放到TODO List里,等待开发完成手头需求后再来这里领新的任务
- 阻塞队列可以是有上限的也可以是没有上限的
非核心线程:
- 也就是非正式员工,需求太多做不过来(阻塞队列满了)才需要用到
- 为什么不招多几个正式员工呢?因为开销大,而且只是应对突然需求过多的场景,如果需求不多的话那么成本(性能)就会有所影响
空闲时间:
- 老板认为非正式员工是能很好解决需求过多的问题,但如果需求变少或者没有的话,那么这一块会是需要考虑的成本问题
- 所以老板决定,等需求TODO List做完后,就让非正式员工遣回外包公司
- 但是又出现一个问题:如果遣回第二天又有很多需求做不完怎么办?来来回回也是很麻烦的?
- 所以老板做出了取舍,等需求TODO List做完后,让非正式员工呆个3天5天再遣回外包公司,防止这段时间又出现太多需求做不完的情况
拒绝策略:
- 背景:有了非正式员工,可以很好解决需求饱和问题,但是如果需求实在多到做不完怎么办呢?
- 当正式员工和非正式员工一起开发需求也做不完(需求TODO List满了),就应该对这些塞不下的需求做拒绝策略
- 拒绝策略有几种:丢弃该需求、抛异常,说加不了了、把需求TODO List最早的任务丢掉、让提需求的人自己开发
总结:
- 要做需求(任务)就需要有开发人员(核心线程)
- 开发人员(线程)做不过来了就先放入需求TODO List等待
- 如果需求TODO List满了,则多招几个外包开发人员(非核心线程)
- 但是外包开发人员(非核心线程)如果在没有需求的情况下,过一段时间(空闲时间)就要遣回外包公司
- 如果需求TODO List和外包开发人员(非核心线程)都满了,就要学会拒绝(拒绝策略)
3、流程图
对应源码为:
public class ThreadPoolExecutor extends AbstractExecutorService {
// ...
public public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 判断工作线程数是否小于corePoolSize
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);
// 如果工作线程数为0,则创建线程(但不执行,因为上面已经offer到队列里了)
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果加入阻塞队列失败,则创建非核心线程执行
else if (!addWorker(command, false))
// 如果非核心线程已满,则触发拒绝策略
reject(command);
}
//...
}