概要
在 HotSpot VM 的线程模型中,Java 线程被一对一映射为内核线程。Java 在使用线程执行程序时,需要创建一个内核线程;当该 Java 线程被终止时,这个内核线程也会被回收。因此 Java 线程的创建与销毁将会消耗一定的计算机资源,从而增加系统的性能开销。
为了解决上述两类问题,Java 提供了线程池概念,线程池的作用总结如下:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
线程池框架Executor
为了更有效地帮助开发人员进行多线程开发,Java 提供了一套 Executor 框架。这个框架中包括了 ScheduledThreadPoolExecutor
和 ThreadPoolExecutor
两个核心线程池。前者是用来定时执行任务,后者是用来执行被提交的任务。这两个线程池的核心原理是一样的,下面重点看看 ThreadPoolExecutor
类是如何实现线程池的。
Executors 实现了以下四种类型的 ThreadPoolExecutor:
线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
状态名 | 高 3位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不接收新任务,会处理阻塞队列剩余任务 |
STOP | 001 | N | N | 会中断正在执行任务并抛弃阻塞队列任务 |
TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入终结 |
TERMINATED | 011 | - | - | 终结状态 |
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就以用一次 cas 原子操作进行赋值
用一个整数的不同位,来保存两种信息(线程的状态+线程的个数),也就是将这两种信息合并成一个cas来操作
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
构造函数
/**
* corePoolSize :
* 核心线程数大小:不管它们创建以后是不是空闲的。
* 线程池需要保持 corePoolSize 数量的线程,除非设置了 allowCoreThreadTimeOut。
*
* maximumPoolSize :
* 最大线程数:线程池中最多允许创建 maximumPoolSize 个线程
* keepAliveTime :
* 存活时间:如果经过 keepAliveTime 时间后,
* 超过核心线程数的线程还没有接受到新的任务,那就回收。
* unit :keepAliveTime 的时间单位
* workQueue
* 存放待执行任务的队列:当提交的任务数超过核心线程数大小后,
* 再提交的任务就存放在这里。它仅仅用来存放被 execute 方法提交的 Runnable 任务。
*
* threadFactory 线程工程:用来创建线程工厂。比如这里面可以自定义线程名称,
*
* handler
* 拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,
* 这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略
救急线程数=maximumPoolSize -corePoolSize
救急线程执行完后会消亡 而核心线程不会;keepAliveTime unit 这两个参数进行控制
*
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
执行流程
其执行过程如下:
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。