线程池:Executor框架源码解析
2.源码解析
ThreadPoolExecutor类*
5)任务执行—execute方法
线程池在使用过程中提交任务使用的是submit
方法,但是该方法本身不是 ThreadPoolExecutor类实现的,而是其父类 AbstractExecutorService类实现的,具体见笔记。该方法内部调用的 execute
方法是需要子类自行实现。
execute
方法的源码如下,
// submit方法向execute方法中传入的参数实际上是FutureTask对象
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 工作的线程小于核心线程数,创建新的线程,成功返回,失败不抛异常
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,就初始化一个线程,这里是个极限情况,入队的时候,突然发现
// 可用线程都被回收了
else if (workerCountOf(recheck) == 0)
// Runnable是空的,不会影响新增线程,但是线程在 start 的时候不会运行
// Thread.run() 里面有判断
addWorker(null, false);
}
// 队列满了,开启线程到 maxSize,如果失败直接拒绝,
else if (!addWorker(command, false))
reject(command);
}
AbstractExecutorService的submit
方法向execute
方法中传入的实际上是newTaskFor
方法创建的 FutureTask类对象,属于Runnable的子类。
该方法分3步对传入的任务进行处理,
- 如果工作线程数小于
corePoolSize
,尝试创建新的线程并将FutureTask类对象传入线程中。这也就解决了初始化时没有在线程池中创建线程的问题。这一过程调用的是 ThreadPoolExecutor的addWorker
方法,将任务加入到任务队列中,该方法具体说明见下方笔记 - 任务被成功加入到任务队列后,再次检查线程池状态(因为最后一次检查可能有线程已经死了)。如果
isRunning
方法返回为 false,则从任务队列中移除刚添加的任务并调用reject
方法拒绝该任务。如果工作线程数为0,则重新创建一个线程 - 如果无法将任务加入到
workQueue
,则尝试调用addWorker
方法,但不作为核心线程。如果依旧无法添加,拒绝该task(此时因为没有加入到workQueue
中,所以无需移除)
6)新建Worker并执行任务—addWorker方法
addWorker
方法传入参数说明,
private boolean addWorker(Runnable firstTask, boolean core)
参数 | 说明 |
---|---|
firstTask | submit -> newTaskFor -> execute -> addWorker,将最初的Runnable或Callable对象封装得到的FutureTask对象 |
core | 是否为核心线程,true表示是核心线程;反之,为非核心线程 |
addWorker
方法的主要执行逻辑如下,分两步进行解读
更新Worker的数量
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
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
}
}
retry
是一个标记,和循环配合使用,continue retry 的时候,会跳到 retry处再次执行。如果 break retry,则跳出整个循环体。前
ThreadPoolExecutor 把状态和线程池数量2个属性存在了ctl
变量中。
- 源码中先检查了线程池状态,线程池状态不正常返回 false
- 然后根据创建线程类型的不同(即是否是核心线程),进行数量的校验。如果数量超过设定数目,返回false
- 通过 CAS方式更新状
ctl
,成功的话则跳出循环。否则再次取得线程池状态,如果和上一次的状态不一致,那么从头开始执行。如果状态并未改变则继续CAS更新 worker 的数量。
数目更新的流程参考下图,
添加worker到workers集合中并且启动worker持有的线程
该过程源码如下,
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
- 将FutureTask对象传入到Worker中,创建新的worker对象
- 可以看到添加 worker 时需要先获得锁,确保并发安全
- 对线程池的状态进行检验。如果线程池状态不对,则调用
addWorkFailed
方法,解锁并返回false - 进一步检验线程是否能够被启动,如果不能,则抛出
IllegalThreadStateException
异常并执行addWorkFailed
方法,结束执行前解锁 - 将worker加入到workers集合中,调用 worker 中封装的线程的
start
方法启动线程。
上述过程失败调用的 addWorkerFailed
方法是对之前操作进行回滚的方法。
7)线程任务实际运行—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 {
while (task != null || (task = getTask()) != null) {
w.lock();
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);
}
}
上述代码具体流程总结如下,
- 取出worker中的 firstTask(FutureTask类对象)赋值给变量task,并使Worker对象的firstTask指向 null
- 如果没有firstTask,则调用
getTask
方法从workQueue
中获取task - 获取 worker对象的锁,因为Worker本身是AQS的子类,所以具有锁的特性
- 执行
beforeExecute
方法,该方法在ThreadPoolExecutor中是空方法,如有需要在子类实现 - 执行
task.run
,调用FutureTask类的run方法。FutureTask的run
方法参见笔记,无论是Runnable还是Callable都能够执行 - 执行
afterExecute
方法,同样在ThreadPoolExecutor中是空方法,如有需要在子类实现 - 内层finally代码块中清空task,worker完成的任务数目+1,释放锁
- 当有异常或者没有 task 可执行时,进入到外层 finally 代码块中调用
processWorkerExit
方法退出当前 worker。从 workers 中移除本 worker 后,如果 worker 数量小于corePoolSize
,则创建新的 worker,以维持corePoolSize
大小的线程数。
下面这行代码
while (task != null || (task = getTask()) != null)
确保了 worker 不停地从 workQueue 中取得 task 执行。getTask
方法会从 workQueue 中 调用poll
或者 take
取出其中的 task。
8)获取workQueue的任务—getTask方法
从任务队列中获取Runnable对象,
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//线程池关闭 && 队列为空,不需要在运行了,直接放回
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// true 运行的线程数大于 coreSize || 核心线程也可以被灭亡
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 队列以 LinkedBlockingQueue 为例,timedOut 为 true 的话说明下面 poll 方法执行返回的是 null
// 说明在等待 keepAliveTime 时间后,队列中仍然没有数据
// 说明此线程已经空闲了 keepAliveTime 了
// 再加上 wc > 1 || workQueue.isEmpty() 的判断
// 所以使用 compareAndDecrementWorkerCount 方法使线程池数量减少 1
// 并且直接 return,return 之后,此空闲的线程会自动被回收
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 从队列中阻塞拿 worker
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 设置已超时,说明此时队列没有数据
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
代码两处关键说明,
- 使用队列的
poll
或take
方法从队列中拿数据,根据队列的特性,队列中有任务可以返回,队列中无任务会阻塞 - 方法中的第二个 if 判断,说的是在满足一定条件下(条件看注释),会减少空闲的线程,减少的手段是使可用线程数减一,并且直接 return null,说明该线程空闲超过时间限制且当前无任务可执行,可销毁该线程。返回后线程结束执行,JVM 会自动回收该线程
3.任务拒绝策略
execute
方法在线程池已满且任务队列已满的条件下会发生线程池拒绝执行任务的情况,调用的是ThreadPoolExecutor的reject
方法,该方法源码如下,
// 方法的参数是传入execute方法的FutureTask类对象
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
上面代码中的handler
对象是初始化过程中指定的拒绝执行处理类,在 ThreadPoolExecutor中实现了4种拒绝执行处理类。这4类拒绝执行类均实现了 RejectedExecutionHandler接口。
RejectedExecutionHandler接口
该接口中只定义了一个方法,
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
ThreadPoolExecutor内部拒绝执行类
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
类名 | 说明 |
---|---|
AbortPolicy | 当线程池和队列都满时,再有任务进来直接抛出RejectedExecutionException 异常,是默认的拒绝处理类 |
CallerRunsPolicy | 当线程池和队列都满时,任务将会被任务的调用方线程执行(如主线程),如果线程池关闭,那么任务将会被抛弃 |
DiscardPolicy | 当线程池和队列都满时,再有任务进来时,直接将任务抛弃且不会有任何返回结果 |
DiscardOldestPolicy | 当线程池和队列都满时,再有任务进来,抛弃最老的未处理的任务,然后重试该新进来的任务,如果线程池关闭,那么任务将会被抛弃 |
使用示例
public static void main(String [] args) throws ExecutionException, InterruptedException {
// 创建一个线程池,分别指定不同的拒绝处理类
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1,
500,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy()
//new ThreadPoolExecutor.DiscardPolicy()
//new ThreadPoolExecutor.DiscardOldestPolicy()
//new ThreadPoolExecutor.AbortPolicy()
);
// 提交第1个任务
Future<String> taskOne = threadPoolExecutor.submit(()->{
System.out.println("taskOne start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("taskOne 沉睡2秒结束...");
return Thread.currentThread().getName();
});
// 提交第2个任务
Future<String> taskTwo = threadPoolExecutor.submit(()->{
System.out.println("taskTwo start...");
return Thread.currentThread().getName();
});
// 提交第3个任务
Future<String> taskThree = threadPoolExecutor.submit(()->{
System.out.println("taskThree start...");
return Thread.currentThread().getName();
});
System.out.println("taskOne:" + taskOne.get());
System.out.println("taskTwo:" + taskTwo.get());
System.out.println("taskThree:" + taskThree.get());
threadPoolExecutor.shutdown();
}
上述例子中创建了一个核心线程池数与最大线程池数都为1,阻塞队列长度也为1的线程池。然后启动三个任务,其中第一个任务执行等待2秒。
taskOne
占用了线程池中的唯一的线程,taskTwo
进入阻塞队列,这时队列已满,taskThree
再进入线程池触发对应的拒绝策略。
- 使用AbortPolicy策略
Exception in thread “main” java.util.concurrent.RejectedExecutionException: …
taskOne start…
taskOne 沉睡2秒结束…
taskTwo start…
主线程在taskThree的submit()
时抛出异常,后面的信息都不打印了
- 使用CallerRunsPolicy策略
taskOne start…
taskThree start…
taskOne 沉睡2秒结束…
taskOne:pool-1-thread-1
taskTwo start…
taskTwo:pool-1-thread-1
taskThree:main
可以看出taskOne
和taskTwo
的执行都是线程池内的那个线程完成的,而taskThree
的执行线程则是调用taskThree
的main函数主线程
- 使用DiscardPolicy策略
taskOne start…
taskOne 沉睡2秒结束…
taskOne:pool-1-thread-1
taskTwo start…
taskTwo:pool-1-thread-1
默默抛弃策略不再打印taskThree
相关信息,并且程序在taskThree.get()
处阻塞,线程池迟迟无法关闭。
- 使用DiscardOldestPolicy策略
taskOne start…
taskOne 沉睡2秒结束…
taskOne:pool-1-thread-1
taskThree start…
taskThree
再进入线程池时,线程池根据策略把未执行的taskTwo
给抛弃了,然后执行了taskThree
,所以打印了 “taskThree start…” 。但是,因为taskTwo
已被抛弃所以在调用taskTwo.get()
时发生了阻塞。所以没有打印main方法中执行后的相关的信息,且线程池始终无法关闭。