一、ThreadPoolExecutor是什么?
ThreadPoolExecutor 是 Java 中的一个线程池实现类。它实现了 ExecutorService 接口,可以用来管理和调度线程执行任务。
线程池是一种用于管理和复用线程的机制,通过维护可重用的线程来执行任务,可以避免频繁地创建和销毁线程,提高了系统的性能和效率。
ThreadPoolExecutor 提供了许多灵活的配置选项,可以根据实际需求来调整线程池的行为。它可以控制线程池的大小、线程的创建和销毁、任务的提交和执行等,还实现了任务的排队、拒绝策略和线程池的状态管理等功能。
二、ThreadPoolExecutor的配置参数
要了解ThreadPoolExecutor的源码,首先必须给先了解ThreadPoolExecutor的七个构造参数。
int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //核心线程最大持续时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //工作队列(用于存放任务)
ThreadFactory threadFactory, //线程池工厂
RejectedExecutionHandler handler //任务满时的拒绝策略
2.1 BlockingQueue<Runnable> workQueue 介绍
workQueue 是用于存放任务的阻塞队列,通常使用 ArrayBlockingQueue & LinkedBlockingQueue ,特殊的线程池会采用 PriorityBlockingQueue(优先级阻塞队列) 或者 DelayQueue(延迟阻塞队列)。当线程池核心线程已经无法再领取任务时会将任务放入到工作队列当中。
2.2 RejectedExecutionHandler handler 介绍
该参数为线程池的拒绝策略,线程池为我们提供了四种拒绝策略,分别为:CallerRunsPolicy、 AbortPolicy、DiscardPolicy、DiscardOldestPolicy 四种, 默认是采用AbortPolicy。这四种拒绝策略的作用如下:
CallerRunsPolicy //阻塞,由当前主线程执行
AbortPolicy //抛出异常,拒绝储存任务
DiscardPolicy //抛弃这条任务
DiscardOldestPolicy //抛弃掉工作队列中最早的任务
三、 ThreadPoolExecutor 的执行流程
了解线程池的源码,先了解线程的执行流程也是很有必要的。ThreadPoolExecutor再添加任务时会经历以下的流程:
四、ThreadPoolExecutor 的 源码解析
4.1 核心属性认知
// ctl 主要是用来储存 线程池的数量 和 线程池的状态
// 前3位用来储存状态, 后29位用来储存线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 用来标记后29位
private static final int COUNT_BITS = Integer.SIZE - 3;
// 最大线程数储存数量 2^29 -1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//线程池的状态
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;
4.2 ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 参数验证,如果参数不对则抛出异常
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
/**
* 给参数赋值
**/
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
//将时间单位改为纳秒
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
4.2 execute 方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取ctl
int c = ctl.get();
// 如果线程的数量少于核心线程,那么添加线程
// 核心线程是有任务来就添加,直到达到配置的数量之后才会复用已经生成的线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//线程池正在运行 且 成功将任务放入工作队列中
if (isRunning(c) && workQueue.offer(command)) {
//dcl 重新获取任务状态
int recheck = ctl.get();
// 如果任务不处于运行中,则删除任务并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果工作线程池位0,则添加一个线程
// 这是为了防止工作队列有数据,但是线程数0出现bug
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果无法添加线程则触发拒绝策略
else if (!addWorker(command, false))
reject(command);
}
4.3 addWorker 方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
// 外层for循环在校验线程池的状态
// 内层for循环是在校验工作线程的个数
// retry是给外层for循环添加一个标记,是为了方便在内层for循坏跳出外层for循环
retry:
for (;;) {
// 获取ctl
int c = ctl.get();
// 拿到ctl的高3位的值
int rs = runStateOf(c);
//==========================线程池状态判断 ==================================================
// 如果线程池状态是SHUTDOWN,并且此时阻塞队列有任务,工作线程个数为0,添加一个工作线程去处理阻塞队列的任务
// 判断线程池的状态是否大于等于SHUTDOWN,如果满足,说明线程池不是RUNNING
if (rs >= SHUTDOWN &&
// 如果这三个条件都满足,就代表是要添加非核心工作线程去处理阻塞队列任务
// 如果三个条件有一个没满足,返回false,配合!,就代表不需要添加
!(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
// 不需要添加工作线程
return false;
for (;;) {
//==========================工作线程个数判断==================================================
// 基于ctl拿到低29位的值,代表当前工作线程个数
int wc = workerCountOf(c);
// 如果工作线程个数大于最大值了,不可以添加了,返回false
if (wc >= CAPACITY ||
// 基于core来判断添加的是否是核心工作线程
// 如果是核心:基于corePoolSize去判断
// 如果是非核心:基于maximumPoolSize去判断
wc >= (core ? corePoolSize : maximumPoolSize))
// 代表不能添加,工作线程个数不满足要求
return false;
// 针对ctl进行 + 1,采用CAS的方式
if (compareAndIncrementWorkerCount(c))
// CAS成功后,直接退出外层循环,代表可以执行添加工作线程操作了。
break retry;
// 重新获取一次ctl的值
c = ctl.get();
// 判断重新获取到的ctl中,表示的线程池状态跟之前的是否有区别
// 如果状态不一样,说明有变化,重新的去判断线程池状态
if (runStateOf(c) != rs)
// 跳出一次外层for循环
continue retry;
}
}
// 添加工作线程以及启动工作线程~~~
// 声明了三个变量
// 工作线程启动了没,默认false
boolean workerStarted = false;
// 工作线程添加了没,默认false
boolean workerAdded = false;
// 工作线程,默认为null
Worker w = null;
try {
// 构建工作线程,并且将任务传递进去
w = new Worker(firstTask);
// 获取了Worker中的Thread对象
final Thread t = w.thread;
// 判断Thread是否不为null,在new Worker时,内部会通过给予的ThreadFactory去构建Thread交给Worker
// 一般如果为null,代表ThreadFactory有问题。
if (t != null) {
// 加锁,保证使用workers成员变量以及对largestPoolSize赋值时,保证线程安全
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 再次获取线程池状态。
int rs = runStateOf(ctl.get());
// 再次判断
// 如果满足 rs < SHUTDOWN 说明线程池是RUNNING,状态正常,执行if代码块
// 如果线程池状态为SHUTDOWN,并且firstTask为null,添加非核心工作处理阻塞队列任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 到这,可以添加工作线程。
// 校验ThreadFactory构建线程后,不能自己启动线程,如果启动了,抛出异常
if (t.isAlive())
throw new IllegalThreadStateException();
// private final HashSet<Worker> workers = new HashSet<Worker>();
// 将new好的Worker添加到HashSet中。
workers.add(w);
// 获取了HashSet的size,拿到工作线程个数
int s = workers.size();
// largestPoolSize在记录最大线程个数的记录
// 如果当前工作线程个数,大于最大线程个数的记录,就赋值
if (s > largestPoolSize)
largestPoolSize = s;
// 添加工作线程成功
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果工作线程添加成功,
if (workerAdded) {
// 直接启动Worker中的线程
t.start();
// 启动工作线程成功
workerStarted = true;
}
}
} finally {
// 做补偿的操作,如果工作线程启动失败,将这个添加失败的工作线程处理掉
if (!workerStarted)
addWorkerFailed(w);
}
// 返回工作线程是否启动成功
return workerStarted;
}
4.4 Worker 工作线程
线程池的工作线程是集成AQS实现的,他也是采用了state来管理线程的状态。
4.4.1 构造参数以及核心属性
// 线程工厂构建的线程
final Thread thread;
// 当前Worker要执行的任务
Runnable firstTask;
// 记录当前工作线程处理了多少个任务。
volatile long completedTasks;
// 有参构造
Worker(Runnable firstTask) {
// 将State设置为-1,代表当前不允许中断线程
setState(-1);
// 任务赋值
this.firstTask = firstTask;
// 基于线程工作构建Thread,并且传入的Runnable是Worker
this.thread = getThreadFactory().newThread(this);
}
4.4.2 runWork方法
final void runWorker(Worker w) {
// 拿到当前线程
Thread wt = Thread.currentThread();
// 从worker对象中拿到任务
Runnable task = w.firstTask;
// 将Worker中的firstTask置位空
w.firstTask = null;
// 将Worker中的state置位0,代表当前线程可以中断的
w.unlock();
// 判断工作线程是否是异常结束,默认就是异常结束
boolean completedAbruptly = true;
try {
// 获取任务
// 直接拿到第一个任务去执行
// 如果第一个任务为null,去阻塞队列中获取任务
while (task != null || (task = getTask()) != null) {
// 执行了Worker的lock方法,当前在lock时,shutdown操作不能中断当前线程,因为当前线程正在处理任务
w.lock();
// 比较ctl >= STOP,如果满足找个状态,说明线程池已经到了STOP状态,该线程即将关闭
// 线程池到STOP状态,并且当前线程还没有中断,确保线程是中断的,进到if内部执行中断方法
// 如果线程池状态不是STOP,确保线程不是中断的。
// 如果发现线程中断标记位是true了,再次查看线程池状态是大于STOP了,再次中断线程
//如果线程池状态 >= STOP,确保线程中断了。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//如果需要在线程池执行任务前后做一些额外的处理,可以重写这个方法
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//执行关闭线程
processWorkerExit(w, completedAbruptly);
}
}
4.5 ThreadPoolExecutor的getTask方法
// 当前方法就在阻塞队列中获取任务
// 前面半部分是判断当前工作线程是否可以返回null,结束。
// 后半部分就是从阻塞队列中拿任务
private Runnable getTask() {
// timeOut默认值是false。
boolean timedOut = false;
// 死循环
for (;;) {
// 拿到ctl
int c = ctl.get();
// 拿到线程池的状态
int rs = runStateOf(c);
// 如果线程池状态是STOP,没有必要处理阻塞队列任务,直接返回null
// 如果线程池状态是SHUTDOWN,并且阻塞队列是空的,直接返回null
if (rs >= SHUTDOWN &&
(rs >= STOP || workQueue.isEmpty())) {
// 先扣减工作线程个数
decrementWorkerCount();
// 返回null,结束runWorker的while循环
return null;
}
// 基于ctl拿到工作线程个数
int wc = workerCountOf(c);
// 核心线程允许超时,timed为true
// 工作线程个数大于核心线程数,timed为true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if (
// 如果工作线程个数,大于最大线程数。
// 即便工作线程个数大于核心线程数了,此时第一次循环也不会为true,因为timedOut默认值是false
// 考虑第二次循环了,因为循环内部必然有修改timeOut的位置
(wc > maximumPoolSize || (timed && timedOut))
&&
// 要么工作线程还有,要么阻塞队列为空,并且满足上述条件后,工作线程才会走到if内部,结束工作线程
(wc > 1 || workQueue.isEmpty())
) {
// 正常结束,工作线程 - 1,因为是CAS操作,如果失败了,重新走for循环
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 工作线程从阻塞队列拿任务
try {
// 如果是核心线程,timed是false,如果是非核心线程,timed就是true
Runnable r = timed ?
// 如果是非核心,走poll方法,拿任务,等待一会
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 如果是核心,走take方法,死等。
workQueue.take();
// 从阻塞队列拿到的任务不为null,这边就正常返回任务,去执行
if (r != null)
return r;
// 说明当前线程没拿到任务,将timeOut设置为true,在上面就可以返回null退出了。
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
五、关于线程池的设计
关于线程池的核心线程设计,一般是考虑你所做的业务是IO密集还是CPU密集。如果是IO密集的话一般要配在2以上,如果比较吹毛求疵的话可以压测来观察最佳配置。如果是CPU密集的话可以一般采用N+1的方法。