接上一篇《Java并发系列(10)——FutureTask 和 CompletionService》
JDK 线程池的实现大致如上图。
Executor 接口方法:
ExecutorService 接口方法:
ScheduledExecutorService 接口方法:
值得一提的是,Executor,ExecutorService,AbstractExecutorService,ScheduledExecutorService 这四个其实不算是线程池。它们仅仅是 Executor,没有规定一定要用线程池实现。
ForkJoinPool,ThreadPoolExecutor,ScheduledThreadPoolExecutor 三个才算是线程池,它们是 ExecutorService 接口的线程池实现。
Executors 类里面的 newFixedThreadPool,newCachedThreadPool 返回的就是这个 ThreadPoolExecutor。
一共 8 个,其中 7 个在构造方法里面。
核心线程数和最大线程数。这两个参数规定了线程池里面工作线程的数量。
正常是 corePoolSize 个工作线程(核心线程),最大可以有 maximumPoolSize 个工作线程(最大线程)。最大线程去掉核心线程的那些,可以理解为“备用线程”,意思是一般情况不使用,只有当并发量太大,核心线程扛不住的时候才会逐渐启动一些备用线程。
工作线程数量的变化可能会经历以下几个阶段:
定义线程空闲时的存活时间。到时间依然没有接到任务,则线程终止。
默认情况,当工作线程数大于核心线程数时,才会终止,低于核心线程数时会一直等待不会终止,除非将 allowCoreThreadTimeOut 参数置为 true。
工作队列,是一个并发阻塞队列,可以有界也可以无界。
来不及处理的任务暂时放在工作队列中。
线程工厂,用于创建线程对象。
当任务被拒绝时,如:
则会将被拒绝的任务交给这个 handler 来处理。
允许核心线程获取任务超时,自动终止。
共五种状态,并且这五种状态存在递进关系。
线程池正常工作,初始态。
在此状态下:
在 shutdown 状态之后,工作线程数会逐渐减少,直到 0,线程池 terminated。
此状态:
terminated 之前的过度状态。
stop 状态下,工作队列已经是空的了,只要等所有工作线程把正在处理的任务处理掉,就可以进入 terminated 状态。
而在进入 terminated 状态之前,会先进入 tidying 状态,然后调用一个 hook 方法(terminated 方法),hook 方法调用后就会进入 terminated 状态。
简单来说,tidying 状态与 terminated 状态之间相差一个 hook 方法的调用。
此状态下:
同 tidying 状态,区别在于 hook 方法已经被调用过了。
初始态:running;
调用 shutdown() 方法:running -> shutdown;
调用 shutdownNow() 方法:running/shutdown -> stop;
tidying 和 terminated 状态没有方法调用,条件达成自然进入:
同时,五种状态从 running 到 terminated 依次递进,只进不退。
提交一个 Runnable 类型的任务,没有返回值。
有几个注意点:
submit 系列有三个方法,都是返回 Future,可以从 Future 里面拿到执行结果。
invoke 系列有四个方法:
TIPS:
做三件事:
做三件事:
并不仅仅是 SHUTDOWN 状态,还包括后面的 STOP,TIDYING,TERMINATED 状态。
TERMINATED 状态。
阻塞方法,等待线程池 TERMINATED,或者超时。
了解了 ThreadPoolExecutor 的核心功能之后,我们来自己实现一个,这样会理解得更加深刻。
ThreadPoolExecutor 的构造方法有 7 个参数,这里我们也把这 7 个参数都用上。
public class LvjcThreadPoolExecutor implements ExecutorService {
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private volatile long keepAliveTime;
private final BlockingQueue<Runnable> workQueue;
private final ThreadFactory threadFactory;
private final LvjcRejectedExecutionHandler rejectedExecutionHandler;
private final HashSet<Worker> workers;
public LvjcThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
LvjcRejectedExecutionHandler handler) {
//*1.规定核心线程数最小为 1(因为如果允许核心线程数为 0 要处理一种特例)
this.corePoolSize = corePoolSize > 0 ? corePoolSize : 1;
//2.规定最大线程数必须 >= 核心线程数
this.maximumPoolSize = Math.max(maximumPoolSize, corePoolSize);
//3.线程最大空闲时间,不管怎么传参,统一转为 ns 单位,便于处理
this.keepAliveTime = keepAliveTime <= 0 || unit == null
? TimeUnit.SECONDS.toNanos(60)
: unit.toNanos(keepAliveTime);
//4.工作队列,设置一个默认的无界阻塞队列
this.workQueue = workQueue == null ? new LinkedBlockingQueue<>() : workQueue;
//5.提供一个默认的线程工厂,自己手写
this.threadFactory = threadFactory == null ? new LvjcDefaultThreadFactory() : threadFactory;
//6.提供一个默认的拒绝处理器,自己手写
this.rejectedExecutionHandler = handler == null ? new DefaultRejectExecutionHandler() : handler;
//7.new 一个容器出来保存工作线程
//要考虑并发安全,但这里不用线程安全容器,因为后面我们会加锁
this.workers = new HashSet<>();
}
}
构造方法里面,我们提供一个默认的线程工厂,实现如下:
package per.lvjc.concurrent.pool;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 默认线程工厂,基本照搬 {@link java.util.concurrent.Executors.DefaultThreadFactory}
*/
public class LvjcDefaultThreadFactory implements ThreadFactory {
private AtomicInteger threadNum;
private String threadNamePrefix;
private ThreadGroup threadGroup;
public LvjcDefaultThreadFactory() {
this.threadGroup = Thread.currentThread().getThreadGroup();
this.threadNamePrefix = "lvjc-pool-thread-";
this.threadNum = new AtomicInteger(1);
}
//实现 ThreadFactory 接口唯一的一个接口方法
@Override
public Thread newThread(Runnable r) {
//把入参 Runnable new 一个 Thread 出去
return new Thread(threadGroup, r, threadNamePrefix + threadNum.getAndIncrement());
}
}
线程工厂的实现比较简单,玩不出什么花样来,JDK 基本上也是这么写的。
主要目的是统一管理线程池里面的线程。
基本需求就是每个线程池都设置一个具有辨识度的名称,这样出了问题一看日志根据线程名称就可以快速定位到属于哪块业务。
package per.lvjc.concurrent.pool;
/**
* 当任务被拒绝时调用。
* 重新定义这个接口,因为我们实现的 LvjcThreadPoolExecutor 跟 jdk 的 ThreadPoolExecutor
* 不是一个类,没有办法传参。
*/
public interface LvjcRejectedExecutionHandler {
void rejectedExecution(Runnable r, LvjcThreadPoolExecutor executor);
}
package per.lvjc.concurrent.pool;
public class DefaultRejectExecutionHandler implements LvjcRejectedExecutionHandler {
//实现唯一的接口方法
@Override
public void rejectedExecution(Runnable r, LvjcThreadPoolExecutor executor) {
//拒绝就算了,我们什么都不干
}
}
因为线程池里的线程是可以复用的,如果直接用 Thread,那么跑一次线程就结束了,没办法复用,所以我们要做一层封装,与前面讲到的 FutureTask 封装 Callable 类似。
怎么实现跑完一个任务的 run 方法不结束线程呢?
基本思路是外面套一层死循环,来一个任务就拿出来处理,没有任务就阻塞等着即可,就像消息队列的消费者。
参照 FutureTask 的实现思路,实现 Runnable 接口,在 run 方法里面做一些特殊处理。
定义一个 Worker 类代表一个工作线程,实现 run 方法:
/**
* 表示线程池里的一个工作线程
*/
public class Worker implements Runnable {
@Override
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTaskFromWorkQueue()) != null) {
//1.执行任务前,把当前线程置为非空闲状态
//其它线程如果想并发干点事情,比如 shutdown,
//看到这个状态,该怎么处理它就有数了。
idle = false;
//2.执行工作任务
try {
task.run();
} finally {
task = null;
}
//3.任务执行完成后,再把当前线程改回空闲状态
idle = true;
}
} finally {
//4.线程结束前的收尾工作
//4.1.清除当前工作线程
executor.getWorkers().remove(this);
//4.2.工作线程数量 -1
executor.decrementWorkerCount();
//4.3.尝试使线程池进入 terminated 状态
//因为 terminated 状态是线程池根据自身运行状况自动进入的,
//所以需要有一些地方来触发线程池检查自身运行状况,看是否需要进入 terminated 状态,
//这里是其中一个触发点
executor.tryTerminate();
}
}
}
然后再补充定义 run 方法里面需要用到的成员变量:
至此,任务消费的主流程就已经实现了。
现在来实现 Worker 的 run 方法里面调用的从队列取走任务的方法。
从阻塞队列取走元素本身很简单,调用 take 或者 poll 方法就可以,但在线程池的场景下,需要多考虑两个问题:
根据上面的思路,实现如下:
private Runnable getTaskFromWorkQueue() {
BlockingQueue<Runnable> workQueue = executor.getWorkQueue();
//先把要用到的参数读出来
int corePoolSize = executor.getCorePoolSize();
int maxPoolSize = executor.getMaximumPoolSize();
long keepAliveTime = executor.getKeepAliveTime();
//需要套一层死循环,不能因为在拿到任务之前被 interrupt 而 return null 出去
for (;;) {
int ctl = executor.getCtl();
int state = LvjcThreadPoolExecutor.runStateOf(ctl);
//1.STOP 状态,直接 return null
if (state == LvjcThreadPoolExecutor.STOP) {
return null;
}
//2.SHUTDOWN 状态,需要做个判断,队列为空才能 return null
//同时,这里不需要担心,此时判断为空,后面又有任务加入队列,
//这种场景不存在,因为是 SHUTDOWN 状态,已经拒绝接受新任务了
if (state == LvjcThreadPoolExecutor.SHUTDOWN && workQueue.isEmpty()) {
return null;
}
//3.能走到这里,肯定是 RUNNING 状态
int workerCount = LvjcThreadPoolExecutor.workerCountOf(ctl);
try {
Runnable runnable;
//3.1.如果当前线程数量 > 核心线程数,等待 keepAliveTime,还没有任务,就 return null
//让当前线程结束,使线程数逐渐缩减到核心线程数,所以调用 poll 方法
if (workerCount > corePoolSize) {
runnable = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
}
//3.2.如果当前工作线程数量 <= 核心线程数,这些线程要保持存活,
//所以调用 take 方法,一直阻塞即可
else {
runnable = workQueue.take();
}
return runnable;
} catch (InterruptedException e) {
//任何原因导致的打断异常,全都吃掉,因为上面的代码已经对所有状态做了处理,
//所以不会有问题
}
}
}
主要是 execute 接口方法,既然是接口方法,只要按接口规范实现即可:
@Override
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
//有 cas 操作通常就得有自旋
for (;;) {
//*用一个 AtomicInteger 同时保存线程池状态和线程总数
//这个实现参考 JDK,后面讲
int c = ctl.get();
//1.如果已经 shutdown/stop/terminated,拒绝接受新任务
if (runStateOf(c) >= SHUTDOWN) {
rejectedExecutionHandler.rejectedExecution(command, this);
return;
}
//2.如果小于 corePoolSize,创建核心线程执行任务
int workerCount = workerCountOf(c);
if (workerCount < corePoolSize) {
try {
//创建线程和 shutdown,shutdownNow 方法是互斥的
//所以这里加锁处理,不加锁也可以实现,但加锁实现比较容易
lock.lock();
//线程总数 +1
if (compareAndIncrementWorkerCount(c)) {
//new 线程同时 start
new Worker(this, threadFactory, command).start();
return;
}
//cas failed
continue;
} finally {
lock.unlock();
}
}
//3.如果大于 corePoolSize,队列未满,扔进队列
if (workQueue.offer(command)) {
return;
}
//4.队列已满,创建备用线程执行任务
//因为距离上次读取线程数已经过去比较久了,
//重新读取线程数,降低 cas 失败的概率
c = ctl.get();
workerCount = workerCountOf(c);
if (workerCount < maximumPoolSize) {
try {
lock.lock();
if (compareAndIncrementWorkerCount(c)) {
new Worker(this, threadFactory, command).start();
return;
}
} finally {
lock.unlock();
}
}
//5.备用线程已用完,拒绝任务
rejectedExecutionHandler.rejectedExecution(command, this);
return;
}
}
shutdown 需要做的是:
@Override
public void shutdown() {
try {
lock.lock();
//1.状态改为 shutdown
advanceState(SHUTDOWN);
//2.打断所有空闲线程
interruptIdleWorkers(false);
} finally {
lock.unlock();
}
//3.触发线程池检测是否能 TERMINATED
tryTerminate();
}
打断空闲线程的实现:
/**
* 打断空闲线程,会被 shutdown 和 tryTerminate 方法调用,
* 其中,shutdown 方法需要打断所有空闲线程;
* tryTerminate 方法只需要打断任意一个即可。
* @param onlyOne
*/
private void interruptIdleWorkers(boolean onlyOne) {
try {
lock.lock();
for (Worker worker : workers) {
if (worker.isIdle()) {
worker.interruptIfStarted();
}
if (onlyOne) {
break;
}
}
} finally {
lock.unlock();
}
}
shutdownNow 需要做的是:
@Override
public List<Runnable> shutdownNow() {
List<Runnable> waitingTasks;
try {
lock.lock();
//1.状态改为 stop
advanceState(STOP);
//2.打断所有线程,类似于打断空闲线程,只需要把线程是否空闲的判断去掉即可
interruptWorkers();
//3.清空工作队列,返回工作队列中的任务
waitingTasks = drainQueue();
} finally {
lock.unlock();
}
//4.也要触发一次 TERMINATED 检测
tryTerminate();
return waitingTasks;
}
因为我们只能 shutdown 一个线程池,而不能 terminate 一个线程池,什么时候应该 terminate 是由线程池自己判断的,所以,我们不能提供 terminate 方法,只能尝试 terminate。
需要考虑,必须满足哪些条件,线程池才可以 terminate:
满足以上三个条件,则意味着所有的任务都处理完了,线程池就可以 terminate 了。
/**
* 检查是否要进入 terminated 状态,
* 线程池进入 terminated 状态的情况:
* 1.shutdownNow 方法被调用,使线程进入了 stop 状态,并且工作线程数量为 0;
* 2.shutdown 方法被调用,使线程进入 shutdown 状态,并且工作线程数量为 0,工作队列里没有任务
* ps:这两种情况实际上是同一种情况:工作线程数量为 0,工作队列没有任务,线程池没有在 running
*/
public void tryTerminate() {
//读取线程池状态
int c = ctl.get();
int state = runStateOf(c);
//1.如果线程池还在 running,不满足第一个条件,不能 terminated
if (state == RUNNING) {
return;
}
//2.如果队列还有任务,不满足第二个条件,也不能 terminated
if (!workQueue.isEmpty()) {
return;
}
//3.如果还有线程活着,把空闲线程打断,促使线程快速终止。
//这里也可以按照上面分析的第三个条件来实现,即遍历所有线程,
//检查是否所有线程都是空闲状态:
//如果是:就 terminate;
//如果不是:就把空闲的线程打断,非空闲的线程等它们跑完。
if (workerCountOf(c) != 0) {
//这里只需要打断一个空闲线程,
//因为能走到这里,说明线程池已经不工作并且工作队列已空,
//这种情况打断一个线程后,被打断的线程会跳出循环,结束工作,
//然后再次触发 tryTerminate,再打断一个线程,被打断的线程结束,又触发 tryTerminate...
interruptIdleWorkers(true);
return;
}
//4.非 running,工作队列没有任务,没有工作线程,进入 terminated 状态
try {
lock.lock();
//更改状态
ctl.set(ctlOf(TERMINATED, 0));
//5.唤醒在等待 terminate 的线程(有个 awaitTermination 方法)
//terminate 成员变量是一个 Condition,
//如果没有使用 lock 实现,那么可以参考 FutureTask,
//用链表保存所有在等待的线程,然后遍历链表 unpark 唤醒也可以。
terminated.signalAll();
} finally {
lock.unlock();
}
}
这个实现就很简单,只要在 Condition 类型的 terminate 成员变量上 await 指定的时间即可:
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
//转换为 ns 单位,因为下面的 awaitNanos 方法只接受 ns 时间
//也可以不转换,不用 awaitNanos 方法,那就需要自己计算一个 deadline
long waitNanos = unit.toNanos(timeout);
try {
lock.lock();
for (;;) {
//如果在指定时间内,等到了线程池 terminate,返回 true
if (isTerminated()) {
return true;
}
//如果指定时间到了,还没等到线程池 terminate,返回 false
if (waitNanos <= 0) {
return false;
}
//awaitNanos 方法,传入需要等待的总时间,
//返回醒来时,剩余还需要等待的时间
waitNanos = terminated.awaitNanos(waitNanos);
}
} finally {
lock.unlock();
}
}
/**
* 要注意,因为线程池各状态之间存在递进关系,
* 只要不是 running 状态,都算 shutdown
* @return
*/
@Override
public boolean isShutdown() {
return runStateOf(ctl.get()) >= SHUTDOWN;
}
@Override
public boolean isTerminated() {
return runStateOf(ctl.get()) >= TERMINATED;
}
除了 execute 方法提交 Runnable 任务,还可以用 submit 提交 Callable 任务。
比较简单,也实现一下:
@Override
public <T> Future<T> submit(Callable<T> task) {
RunnableFuture<T> futureTask = new FutureTask<>(task);
execute(futureTask);
return futureTask;
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
RunnableFuture<T> futureTask = new FutureTask<>(task, result);
execute(futureTask);
return futureTask;
}
@Override
public Future<?> submit(Runnable task) {
RunnableFuture<?> futureTask = new FutureTask<>(task, null);
execute(futureTask);
return futureTask;
}
submit 系列方法就是简单地用 FutureTask 封装一下就可以。
invoke 系列方法稍微麻烦一些,但差不多也是一个套路,只是变成批量提交任务,这里就不再自己实现了。
实现思路都是一样的,JDK 的实现多考虑了很多小细节,以及某些地方实现更优雅。
JDK 使用一个 int 数据同时存储了线程状态和线程数两个信息:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
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;
// Packing and unpacking ctl
private static int runStateOf(int c) {
return c & ~CAPACITY; }
private static int workerCountOf(int c) {
return c & CAPACITY; }
private static int ctlOf(int rs, int wc) {
return rs | wc; }
这样做会带来一些好处:
我们上面自己实现的时候,限制了 corePoolSize 最小是 1,而 JDK 的实现是可以为 0 的。
实际上 Executors 工具类里面的 newCacheThreadPool 就是这么用的:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这样就有一个疑问。
前面说,提交新任务时:
那么如果核心线程数为 0,按照上面的说法,它应该会把任务加入队列,等队列放满,而不是创建备用线程来处理。
可以测试真实情况并非如此,看看 JDK 是怎么处理的。
提交任务在 execute 方法里面:
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)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
所以,前面的那个说法并不完全正确。
存在一个特例,队列未满就会创建备用线程,就是核心线程数为 0 的情况。
这跟核心数为 1 还是有所区别的:
我们前面没有实现核心线程超时的功能,实际上,ThreadPoolExecutor 是允许核心线程超时的。
private Runnable getTask() {
...
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
...
}
在 getTask 方法里面有这么一个处理,如果 allowCoreThreadTimeOut 参数为 true,不管当前线程数是不是大于核心线程数,都会超时销毁。
总结一下核心线程会被销毁的情况:
默认情况,核心线程的创建是懒惰模式的,不来任务不创建。
但也支持提前创建。
提前创建一个核心线程:
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
提前创建所有核心线程:
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
有两个,一个必须要等所有任务执行完,另一个支持超时提前结束。
这里只看超时方法:
//提交一批任务,等到所有任务执行完成,返回所有任务的 Future,与传入的 Callable 顺序一一对应
//如果超时没有全部完成,提前返回,没有拿到结果的任务全部取消。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout);
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
//1.把所有任务包装成 FutureTask
for (Callable<T> t : tasks)
futures.add(newTaskFor(t));
final long deadline = System.nanoTime() + nanos;
final int size = futures.size();
// Interleave time checks and calls to execute in case
// executor doesn't have any/much parallelism.
//2.遍历提交所有 FutureTask,在提交 FutureTask 的过程中就可能超时
for (int i = 0; i < size; i++) {
execute((Runnable)futures.get(i));
nanos = deadline - System.nanoTime();
if (nanos <= 0L)
//超时提前返回结果
return futures;
}
//3.依次阻塞等待获取所有 FutureTask 的执行结果,这里也可能超时
for (int i = 0; i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
if (nanos <= 0L)
return futures;
try {
f.get(nanos, TimeUnit.NANOSECONDS);
} catch (CancellationException ignore) {
//吃掉取消任务异常,避免影响后面的任务
} catch (ExecutionException ignore) {
//吃掉任务执行失败异常,避免影响后面的任务
} catch (TimeoutException toe) {
//超时提前返回结果
return futures;
}
nanos = deadline - System.nanoTime();
}
}
//4.走到这里,所有任务都已执行完成
done = true;
return futures;
} finally {
if (!done)
//5.如果任务没有来得及全部执行完成,cancel(true) 所有任务
//回顾一下上一章讲过的 cancel(true):
//对已经有结果的任务(NORMAL/EXCEPTIONAL 状态),没有任何影响;
//对尚未执行的任务(NEW 状态),置为 INTERRUTED 最终态,任务不会再执行;
//对正在执行的任务(NEW 状态),将其打断,置为 INTERRUPTED 最终态,
// 如果任务响应打断,抛出 InterruptedException,但不会保存结果;
// 如果任务不响应打断,继续执行,但执行完也不会保存结果。
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
invokeAny 方法也有两个,其中一个支持超时:
//批量提交一批任务,有任意一个成功执行完成,都会返回,返回值为任务结果
//超时未得到结果抛异常,取消所有任务
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
主要逻辑在 doInvokeAny 方法:
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
//注意这里用了上一章讲过的 CompletionService
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
// For efficiency, especially in executors with limited
// parallelism, check to see if previously submitted tasks are
// done before submitting more of them. This interleaving
// plus the exception mechanics account for messiness of main
// loop.
try {
// Record exceptions so that if we fail to obtain any
// result, we can throw the last exception we got.
ExecutionException ee = null;
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Iterator<? extends Callable<T>> it = tasks.iterator();
// Start one task for sure; the rest incrementally
//1.先提交第一个任务
futures.add(ecs.submit(it.next()));
--ntasks;
int active = 1;
//2.循环提交剩下的任务
for (;;) {
//2.1.在提交新任务之前,先尝试读取一下已经提交的任务有没有已经完成的
//ecs.poll() 会从任务执行结果队列里面尝试取出第一个结果
Future<T> f = ecs.poll();
//2.2.如果所有已经提交的任务都还未完成
if (f == null) {
//2.2.1.如果还有任务没有提交
if (ntasks > 0) {
//再提交一个任务看看
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
//2.2.2.如果所有任务都已经提交,并且都已经执行完成,但又没拿到结果
else if (active == 0)
//跳出循环,没机会了,所有任务全都抛异常,没有一个成功执行完的
break;
//2.2.3.如果所有任务都已经提交,但还未全部执行完,而且有超时时间
else if (timed) {
//在超时之前,等一个结果
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
//时间到,醒来没结果,超时异常
//不要担心被 interrupt,interrupt 会抛异常,不会走到这里
if (f == null)
throw new TimeoutException();
//时间还没到,提前拿到结果,继续往下跑
nanos = deadline - System.nanoTime();
}
//2.2.4.如果所有任务都已经提交,但还未全部完成,而且没有超时时间
else
//一直等,等到出结果为止
f = ecs.take();
}
//2.3.拿到了一个任务的结果
//可能是在 2.1 那一步拿到的;
//也可能是在 2.2.3,2.2.4 那两处拿到的
if (f != null) {
--active;
try {
//把结果取出来,这里的 get 不会阻塞,
//CompletionService 里面取出来的一定是执行完成的
//如果结果是成功执行的,就在这里返回了
return f.get();
} catch (ExecutionException eex) {
//这是个抛异常的结果,保存异常,继续循环等下一个任务结果
ee = eex;
} catch (RuntimeException rex) {
//其它为止运行时异常,保存异常,继续循环等下一个任务结果
ee = new ExecutionException(rex);
}
}
}
//3.所有任务的结果都已经出来了,但没有在上面 return 出去
//说明所有任务全都失败了
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
//4.拿到正确的结果,或者超时,或者所有任务全都失败
//不管怎样,cancel(true) 所有任务
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
JDK 的实现里面还留下了一些 hook 方法便于子类扩展。
主要有三个:
注意:以上三个方法里面如果抛出异常,外面不会 catch,会在 finally 逻辑执行完后再抛出去。
ThreadPoolExecutor 已经提供了 4 个 RejectedExecutionHandler。
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) {
//线程池如果 shutdown 不处理
//也就是只处理因队列满和线程满而来不及处理的任务;
//因线程池 shutdown 而被拒绝的任务直接丢弃
if (!e.isShutdown()) {
//在调用方线程直接调 run 方法
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) {
//也是不处理因 shutdown 而被拒绝的任务
if (!e.isShutdown()) {
//把对列头部的任务挤掉一个
e.getQueue().poll();
e.execute(r);
}
}
}