为什么要用线程池
- 降低资源消耗,提高响应速度
池化将线程统一在一起,避免了创建/释放线程的损耗 - 提高线程可管理性,合理配置资源
防止对资源的无限申请,对线程合理的调度及监控 - 线程池具有可扩展性
线程池结构
这里简单说明一下:线程池如何将任务与线程分离。实际上,线程池内部采用生产者/消费者模式。生产者是任务管理,消费者是任务执行。
构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
参数 | 作用 |
---|---|
corePoolSize | 核心线程数 |
maximumPoolSize | 最大线程数 |
workQueue | 任务队列 |
keepAliveTime | 空闲线程保活时间 |
ThreadFactory | 提供线程 |
handler | 线程池满但有新任务提交时策略 |
线程池生命周期
线程池中有两个重要参数管理线程池的生命周期:线程池运行状态runState
和 线程数量workerCount
,这两个参数在同一变量的不同位上
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
高3位保存runState,低29位保存workerCount。这样一次CAS,设置两个变量,相比每个变量一个CAS,岂不双倍快乐。
线程池的运行状态有五种
运行状态 | 说明 |
---|---|
RUNNING | 可以提交新的任务,也能处理阻塞队列 |
SHUTDOWN | 线程池处于关闭状态, |
STOP | |
TIDYING | 所有任务都已终止,有效线程数为0 |
TERMINATED | 调用terminated()之后进入该状态 |
任务执行机制
这里强调线程池如何管理任务。线程池中任务和线程解耦,二者互不相关。线程池安排任务的调度,将任务分配给线程池中的线程
任务调度
public void execute(Runnable command)
If a task can be successfully queued, then we still need to double-check whether we should have added a thread (because existing ones died since last checking) or that the pool shut down since entry into this method. So we recheck state and if necessary roll back the enqueuing if stopped, or start a new thread if there are none.
即使成功加入阻塞队列,在申请新的线程时仍要二次检查,有可能线程在上一次检查后挂了,也有可能线程池在上一次检查后停了。所以我们要重新检查状态,并在必要的时候让Task重新排队,或是开启新的线程
任务缓冲
当线程池来不及处理任务时,会将其暂时存放在阻塞队列中,等候处理。
使用不同的阻塞队列可以实现不一样的任务存取策略。线程池支持的队列模式有:
null
从我们使用线程池的出发点:对资源的合理管控 来说,最好不要使用不限长的阻塞队列,如果有过多的任务申请,可能导致资源空间不限制的被占用,影响系统稳定性
任务申请
private Runnable getTask()
任务执行存在两种可能,一种是新建线程执行,另一种是已有线程从阻塞队列中获取。这里对第二种情况进行说明,任务申请时,线程池将严格控制工作线程的数量
任务拒绝
拒绝策略是一个接口
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略
线程执行过程
这部分强调线程池如何管理线程,并维护线程在线程池中生命周期(不同于线程本身的生命周期)
线程添加
private boolean addWorker(Runnable firstTask, boolean core)
- firstTask : 新增线程执行的第一个任务
- core : true 以corePoolsize为容量
false 以maximumPoolSize为容量
线程执行任务
final void runWorker(Worker w)
线程回收
线程池中线程回收依赖JVM实现,线程池中只维护线程的引用,若线程可回收时,删除引用,等待JVM释放
线程池的执行过程
应用
- 快速响应
响应优先,需要把用户发起的响应尽可能快的发送,故不应当设置消息队列,且corePoolsize
和maxPoolSize
尽可能大 - 快速处理批量业务
吞吐量优先,要求最大限度利用有限资源 (因为肯定快速响应不完,正所谓 穷则战术穿插,达则给老子炸,要是能一下处理完还用受这委屈?再说了,反正处理不完,只要别慢的太离谱就行,留几个干别的别人也看不出来)
需要合理设置阻塞队列、最大线程数和核心线程数,避免过多上下文切换影响速度
动态化线程池设计
- 动态调参
能根据情况调整线程数量 队列的长度,避免线程池资源不足/资源过剩 - 任务监控
监控任务的执行情况,记录任务的执行时间及平均时间,用于优化任务的合并于拆分 - 负载警告
能在队列积压过多/线程池频繁高负载是发出警告 - 修改记录
记录每次修改位置及修改原因,便于回滚 - 权限校验
防止无关/恶意人员对线程池破坏
参考链接: