虽然看过线程池的代码, 但是实际工作中都是用的工具类, 很少实际真正意义上用过线程池. 这次有个需求要对线程池做一点改动, 要改动, 肯定得非常熟悉线程池的工作流程.
假设线程池正常工作的场景下阅读线程池的工作流程
一、ThreadPoolExecutor
首先分析构造函数中参数的含义, 如果搞明白这些, 其实就不需要死记Executors工具类中例如fixed, schedule这种提供的线程池的作用了.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
- corePoolSize: 线程池中核心线程数量
- maximumPoolSize: 线程池中最大线程数量
- keepAliveTime:
- workQueue: 任务队列
到目前为止一脸懵逼, 一定要结合线程池的工作流程来分析这几个参数.
二、execute提交任务
2.1 execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.threads < corePoolSize, 创建线程执行该任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.threads >= corePoolSize时, 线程先入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3.线程创建失败, 拒绝该Task
else if (!addWorker(command, false))
reject(command);
}
这里涉及三个步骤:
1、如果线程池中线程数量threadSize < 核心线程corePoolSize, 则对该command进行addWorker处理
2、如果threadSize < corePoolSize且addWorker处理该Task失败 或者 threadSize > corePoolSize, 此时进行workQueue.offer即任务入队操作, 入队成功, 判断当前线程池中线程数量, 如果为0再次尝试执行addWorker操作.
3、第二步中如果入队失败, 对该task进行addWorker处理.
三次addWorker,分别传入的参数是(command, true), (null, false), (command, false).
2.2 addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
for (;;) {
int wc = workerCountOf(c);
/**
* 1.结合execute中调用addWorker的三种场景, 只有第一种场景threadSize < corePoolSize时,
* 调用addWorker传入的参数为true.
* 2.所以结合这里当core为true时, wc < corePoolSize.
* 3.core = false, 且wc >= maximumPoolSize的场景:
* 任务队列满了之后, Task进入不了workQueue, 此时会创建非核心线程执行task, 直到
* wc >= maximumPoolSize.
*/
if (wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
/**
* 结合上面的for循环, 如果能执行到这里, 有两种情况:
* 1.core为true且wc < corePoolSize;
* 2.core为false且wc < maximumPoolSize;
*/
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
/**
* 结合execute中的三种场景, 只有第二种场景传入的firstTask为null, 满足这种场景的前提是:
* 1. wc > corePoolSize;
* 2. workQueue.offer(firstTask)为true, 也就是说任务队列未满
* 此时构造Worker时传入了firstTask = null, 考虑一个问题, Worker如何执行firstTask?
*/
// 到这里可以知道Worker与Thread是1:1的关系
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
// Worker能否执行的标识位
workerAdded = true;
} finally {
mainLock.unlock();
}
// 如果Worker能够被执行, 也就是被添加到workers中, 执行该worker
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
分析到这里, 大致明白addWorker在什么场景下会返回true/false
1、addWorker = true: (1)core = true且wc < corePoolSize, (2)core = false且wc < maximumPoolSize;
2、addWorker = false: core = false且wc > maximumPoolSize;
接下来分析执行线程的流程
三、任务被执行的流程
3.1 runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
/**
* 1.如果task不为空, 直接调用task.run()
* 2.如果task为空, 也就是execute()中的场景二, 通过getTask()获取该task,
* 然后调用task.run()
*/
while (task != null || (task = getTask()) != null) {
w.lock();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 执行完之后释放worker.
processWorkerExit(w, completedAbruptly);
}
}
1、结合execute方法中调用addWorker()的三种场景, 如果传入的参数是(task, true)、(task,false)时, addWorker中构建Worker时会将该task传入, 然后在runWorker中task != null, 直接调用该task.run()
2、传入(null, true)时, 此时首先需要将task进行入队, execute中的workQueue.offer操作, 然后runWorker中再通过getTask()获取该task, 然后执行task.run()
3.2 getTask()获取任务
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
int wc = workerCountOf(c);
// allowCoreThreadTimeOut: 是否允许核心线程超时
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
try {
// 这里采用了AQS锁以及相关集合的特性:
// 1.poll(timeParams)阻塞当前线程timeParams时间
// 2.take()会一直阻塞当前线程, 直到workQueue中有数据进来.
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
1、这里涉及到了AQS锁的特性, 如果timed = true允许超时, 则调用workQueue.poll将当前线程阻塞 keepAliveTime 时间, 然后再唤醒该线程, 而keepAliveTime与TimeUnit.NANOSECONDS就是ThreadPoolExecutor构造函数中传入的两个参数
2、如果timed = false, 也就是不允许超时, 则当前线程会被一直阻塞.
3、timed与allowCoreThreadTimeOut、wc > corePoolSize两个因素有关, 第一个参数表示是否允许核心线程超时, 第二个条件表示当前线程池中的线程数量是否大于核心线程数量, 这两个参数结合起来也就是(1)如果允许核心线程超时, 且队列为空时, 核心线程挂起keepAliveTime时间, 然后销毁.(2)非核心线程且队列为空时, 挂在keepAliveTime时间, 然后停止运行.
4、allowCoreThreadTimeOut涉及到allowCoreThreadTimeOut(boolean value)方法, 线程池提供接口运行自定义是否允许核心线程超时, 默认情况下, allowCoreThreadTimeOut = false, 既不允许核心线程超时.
四、考虑以下几个问题:
1、如果线程池中的线程数量 < corePoolSize, 且线程处于挂起状态, 此时如果有新任务时, 线程池是如何处理?
2、如果线程池中线程数量 > corePoolSize时, 此时如何处理新来的任务?
4.1 针对问题一
结合execute与addWorker其实可以知道, 如果workers < corePoolSize时, 流程中并没有对挂起的核心线程进行唤醒操作, 而是通过addWorker直接创建线程操作, 也就是说, 只要线程池中的workers < corePoolSize, 新来任务时, 就会创建线程执行该Task.
4.2 针对问题二
1、当workers > corePoolSize时, 通过execute添加新的task时, 会首先将该task添加到workQueue中, 如果添加成功, 表明任务队列未满, 此时根据AQS锁的机制, 会唤醒调用getTask()被阻塞的线程, 线程被唤醒之后, 从队列中获取task并执行, 从而达到线程复用的目的.
2、但是到这里又有一个疑问了, 先贴出execute中的第二种场景
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
执行到这里, 说明当前线程池中 threads >= corePoolSize, 此时只知道线程池中线程的数量关系, 线程的状态(运行, 挂起)不可知, 这个其实不用太关心, 只需要知道workQueue.offer将task添加到任务队列, 是为了唤醒在getTask()处挂起的线程, 让线程能够复用. 既然是复用, 那为何还要再次判断workerCountOf(rechcke) == 0操作呢? 如果为true, 则调用addWorker, 如果线程池正常运行, 且 threads < maximumPoolSize 则addWorker是一定会创建一个线程的.
网上看到很多文章介绍到这里都是说, 通过workerCountOf(rechcke) == 0判断如果为true, 表示没有线程, 然后通过addWorker创建线程执行该task, 那有没有这种情况, workQueue.offer唤醒了挂起的线程执行该Task, 然后在workerCountOf(recheck) == 0之前, 当前线程执行完task并且销毁, 此时workerCountOf(recheck) == 0为true, 而workerQueue并没有task. 既然这样, addWorker的意义何在?
这里先记录留下问题, 个人理解也不知道对不对, 以后如果有能力再矫正这里的观点, 此时如果workerCount == 0为true, 也就是当前线程池中并没有可用线程, 此时不管workQueue是否为空, 都调用addWorker创建一个新的线程, 如果workQueue不为空, 则从workQueue中取任务执行, 如果workQueue为空, 则预创建一个线程以待执行下次execute提交的task.