- Java 线程池有哪些属性?
- 提交任务时队列满了怎么办?
- 什么情况下会创建新线程?
- 提交任务时线程池满了怎么办?
- 空闲线程怎么关闭?
- 线程池执行顺序是怎样的?如何让线程池按照 core、max、queue 的执行顺序去执行?
- Executors.newFixedThreadPool(...) 和 Executors.newCachedPool(...) 构造出来的线程池有哪些差异?
- 子线程抛出的异常,主线程能感知到吗?任务执行过程中发生异常,怎么处理?
- 什么时候会执行拒绝策略?
- 任务执行结果是怎么获取的?流程是怎样的?
总览
先看下 Java 线程池几个相关类的继承结构:
Executor -> ExecutorService -> AbstractExecutorService -> ThreadPoolExecutor 的继承结构。
Executor
Executor 接口非常简单只有一个 void execute(Runnable command) 方法代表提交一个任务。
我们经常这样显式的启动一个线程:
new Thread(new Runnable(){
// do something
}).start();
使用线程池后我们可以这样执行任务:
Executor executor = new SerialExecutor();
executor.execute(() -> {...});
如果我们希望线程池同步执行任务,我们可以这样实现 Executor 接口:
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();// 这里不是用的 new Thread(r).start(),也就是说没有启动任何一个新的线程。
}
}
想要异步执行任务:
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start(); // 每个任务都用一个新的线程来执行
}
}
看下如何实现,所有任务加入一个 queue 中,然后从 queue 中取任务交给真正的 Executor 执行:
public class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<>(10);
final Executor executor;
Runnable active;
public SerialExecutor(Executor executor) {
this.executor = executor;
}
// 提交任务到线程池:将任务添加到任务队列,scheduleNext 触发执行器去任务队列取任务执行
@Override
public synchronized void execute(@Nonnull Runnable command) {
tasks.offer(() -> {
try {
command.run();
} finally {
this.scheduleNext();
}
});
}
private void scheduleNext() {
if ((active = tasks.poll()) != null) {
// 具体执行转给真正的执行器
executor.execute(active);
}
}
}
Executor 只有提交任务功能,没法实现查看当前任务队列剩余多少任务?任务队列满了怎么办?当前线程池有多少线程活着?都是这个接口不足之处,所以要介绍下面这个接口:ExecutorService。
ExecutorService
一般我们定义一个线程池时,往往都是使用的这个接口:
ExecutorService executor = Executors.newFixedThreadPool(args...);
ExecutorService executor = Executors.newCachedThreadPool(args...);
先简单过一下这个接口有哪些方法。
public interface ExecutorService extends Executor {
// 关闭线程池,已提交的任务继续执行,不继续接收新任务
void shutdown();
// 关闭线程池,非但不继续接收新任务,还将停止当前正在执行的任务
List<Runnable> shutdownNow();
// 线程池是否关闭
boolean isShutdown();
// 如果调用了 shutdown() 或 shutdownNow() 后所有任务都结束了,则返回 true
// 这个方法必须在 shutdown() 或 shutdownNow() 调用之后才会返回 true
boolean isTerminated();
// 等待所有任务完成,并设置超时时间
// 实际应用是:调用关闭线程池方法后,调用这个方法阻塞等待池中所有任务完成。
// 当超时或者发起 awaitTermination 的线程被中断时,从阻塞等待中返回。
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交一个 Callable 任务
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务,第二个参数会放到 FutureTask 中,作为返回值
// 因为 Runnable 的 run 方法并不返回任何东西
<T> Future<T> submit(Runnable task, T result);
// 提交一个 Runnable 任务
Future<?> submit(Runnable task);
// 执行所有的任务,返回 Future 集合
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 执行所有任务,但带有超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 只要有一个任务结束了就返回,返回执行完的那个任务的结果
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 同只要有一个任务结束了就返回,返回执行完的那个任务的结果
// 但这个带超时,超过执行时间后抛出 TimeoutException
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
ExecutorService 提供了比 Executor 接口更丰富的方法,能提交任务,关闭线程池,阻塞等待线程池所有任务都结束,能获取结果。
FutureTask
在继续介绍 ExecutorService 实现类前,先来说说相关类 FutureTask,提交给线程池的任务 Runnable 都是先封装成 FutureTask。
Future Runnable
\ /
\ /
RunnableFuture
|
|
FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
// runnable 被适配成 Callable 实现类是Executors.RunnableAdapter
private Callable<V> callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
.....
}
# Executors.RunnableAdapter 503
static final class RunnableAdapter<T> implements Callable<T> {
// 传入的 runnable
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
FutureTask 通过 RunnableFuture 间接实现了 Runnable,所以每个 Runnable 通常是经过 RunnableAdapter 适配成 Callable 转换成 FutureTask 类型。然后调用 executor.execute() 方法将任务 FutureTask 提交给线程池。
Runnable 方法是没有返回值的,可以在 submit 的第二个参数指定一个返回值T实例对象(不是 Class 类型)同时要求 Runnable 内组合一个 T 实例对象,通过构造方法传入。
<T> Future<T> submit(Runnable task, T result);
Mail mail = new Mail();
// 传入相同的对象,run 方法可以间接使用该对象通信
Future<Mail> future = pool.submit(new MessageRunnable(mail), mail);
System.out.println(future.get().getTitle());
private static class MessageRunnable implements Runnable {
private Mail mail;
private MessageRunnable(Mail mail) {
this.mail = mail;
}
@Override
public void run() {
mail.setTitle("123");
}
}
另外还可以实现 Callable 接口,传回返回值。当运行出现异常时,call 会抛出异常但 run 不会抛异常。
public abstract void run();
V call() throws Exception;
下面我们来看 ExecutorService 的抽象实现 AbstractExecutorService。
AbstractExecutorService
AbstractExecutorService 抽象类派生自 ExecutorService 接口,实现了 submit 三个复载方法,invokeAny, invokeAll 方法,以及添加了 两个 newTaskFor 方法。顶层 Executor 接口的 execute void execute(Runnable command) 不需要返回结果,所以不会封装成 FutureTask。
// newTaskFor-Callable
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// newTaskFor-Runnable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
AbstractExecutorService 抽象类中 invokeAll invokeAny 占方法体绝大部分篇幅,实战使用很少不具备文章承前启后功能,略过。
public abstract class AbstractExecutorService implements ExecutorService {
// RunnableFuture 是用于获取执行结果的,我们常用它的子类 FutureTask
// 下面两个 newTaskFor 方法用于将我们的任务包装成 FutureTask 提交到线程池中执行
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// 提交任务
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 2. 交给执行器执行,execute 方法由具体的子类来实现
// 前面也说了,FutureTask 间接实现了Runnable 接口。
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task, result);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
// 此方法目的:将 tasks 集合中的任务提交到线程池执行,任意一个线程执行完后就可以结束了
// 第二个参数 timed 代表是否设置超时机制,超时时间为第三个参数,
// 如果 timed 为 true,同时超时了还没有一个线程返回结果,那么抛出 TimeoutException 异常
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();
//
List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);
// ExecutorCompletionService 不是一个真正的执行器,参数 this 才是真正的执行器
// 它对执行器进行了包装,每个任务结束后,将结果保存到内部的一个 completionQueue 队列中
// 这也是为什么这个类的名字里面有个 Completion 的原因吧。
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
try {
// 用于保存异常信息,此方法如果没有得到任何有效的结果,那么我们可以抛出最后得到的一个异常
ExecutionException ee = null;
long lastTime = timed ? System.nanoTime() : 0;
Iterator<? extends Callable<T>> it = tasks.iterator();
// 首先先提交一个任务,后面的任务到下面的 for 循环一个个提交
futures.add(ecs.submit(it.next()));
// 提交了一个任务,所以任务数量减 1
--ntasks;
// 正在执行的任务数(提交的时候 +1,任务结束的时候 -1)
int active = 1;
for (;;) {
// ecs 上面说了,其内部有一个 completionQueue 用于保存执行完成的结果
// BlockingQueue 的 poll 方法不阻塞,返回 null 代表队列为空
Future<T> f = ecs.poll();
// 为 null,说明刚刚提交的第一个线程还没有执行完成
// 在前面先提交一个任务,加上这里做一次检查,也是为了提高性能
if (f == null) {
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
// 这里是 else if,不是 if。这里说明,没有任务了,同时 active 为 0 说明
// 任务都执行完成了。其实我也没理解为什么这里做一次 break?
// 因为我认为 active 为 0 的情况,必然从下面的 f.get() 返回了
// 2018-02-23 感谢读者 newmicro 的 comment,
// 这里的 active == 0,说明所有的任务都执行失败,那么这里是 for 循环出口
else if (active == 0)
break;
// 这里也是 else if。这里说的是,没有任务了,但是设置了超时时间,这里检测是否超时
else if (timed) {
// 带等待的 poll 方法
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
// 如果已经超时,抛出 TimeoutException 异常,这整个方法就结束了
if (f == null)
throw new TimeoutException();
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
// 这里是 else。说明,没有任务需要提交,但是池中的任务没有完成,还没有超时(如果设置了超时)
// take() 方法会阻塞,直到有元素返回,说明有任务结束了
else
f = ecs.take();
}
/*
* 我感觉上面这一段并不是很好理解,这里简单说下。
* 1. 首先,这在一个 for 循环中,我们设想每一个任务都没那么快结束,
* 那么,每一次都会进到第一个分支,进行提交任务,直到将所有的任务都提交了
* 2. 任务都提交完成后,如果设置了超时,那么 for 循环其实进入了“一直检测是否超时”
这件事情上
* 3. 如果没有设置超时机制,那么不必要检测超时,那就会阻塞在 ecs.take() 方法上,
等待获取第一个执行结果
* 4. 如果所有的任务都执行失败,也就是说 future 都返回了,
但是 f.get() 抛出异常,那么从 active == 0 分支出去(感谢 newmicro 提出)
// 当然,这个需要看下面的 if 分支。
*/
// 有任务结束了
if (f != null) {
--active;
try {
// 返回执行结果,如果有异常,都包装成 ExecutionException
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}// 注意看 for 循环的范围,一直到这里
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
// 方法退出之前,取消其他的任务
for (Future<T> f : futures)
f.cancel(true);
}
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
// 执行所有的任务,返回任务结果。
// 先不要看这个方法,我们先想想,其实我们自己提交任务到线程池,也是想要线程池执行所有的任务
// 只不过,我们是每次 submit 一个任务,这里以一个集合作为参数提交
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
// 这个很简单
for (Callable<T> t : tasks) {
// 包装成 FutureTask
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
// 提交任务
execute(f);
}
for (Future<T> f : futures) {
if (!f.isDone()) {
try {
// 这是一个阻塞方法,直到获取到值,或抛出了异常
// 这里有个小细节,其实 get 方法签名上是会抛出 InterruptedException 的
// 可是这里没有进行处理,而是抛给外层去了。此异常发生于还没执行完的任务被取消了
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
// 这个方法返回,不像其他的场景,返回 List<Future>,其实执行结果还没出来
// 这个方法返回是真正的返回,任务都结束了
return futures;
} finally {
// 为什么要这个?就是上面说的有异常的情况
if (!done)
for (Future<T> f : futures)
f.cancel(true);
}
}
// 带超时的 invokeAll,我们找不同吧
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
if (tasks == null || unit == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout);
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks)
futures.add(newTaskFor(t));
long lastTime = System.nanoTime();
Iterator<Future<T>> it = futures.iterator();
// 每提交一个任务,检测一次是否超时
while (it.hasNext()) {
execute((Runnable)(it.next()));
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
// 超时
if (nanos <= 0)
return futures;
}
for (Future<T> f : futures) {
if (!f.isDone()) {
if (nanos <= 0)
return futures;
try {
// 调用带超时的 get 方法,这里的参数 nanos 是剩余的时间,
// 因为上面其实已经用掉了一些时间了
f.get(nanos, TimeUnit.NANOSECONDS);
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
} catch (TimeoutException toe) {
return futures;
}
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
}
done = true;
return futures;
} finally {
if (!done)
for (Future<T> f : futures)
f.cancel(true);
}
}
}
到这里我们发现 invokeAll, invokeAny, submit 方法都只是将任务 FutureTask 通过 execute 方法提交给线程池,还没有出现真正开启线程执行任务的 execute 方法体,这个就在下文 ThreadPoolExecutor 类中。
ThreadPoolExecutor
概览
ThreadPoolExecutor 是 JDK 中线程池的实现,它实现了任务提交,任务管理、监控方法。我们先回顾下提交任务的三个方法:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
一个最基本的概念,submit 方法中的参数 Runnable, Callable 不适用于 new Thread(runnable).start() 开启线程用的 runnable,而是指定线程负责的任务,线程要做的事情在 Runnable 的 run 或者 Callable 的 call 方法中指定。
通过 Javadoop 博主的配图看下线程池的主要构件:
构造方法
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
- TimeUnit:超时时间单位
- keepAliveTime:空闲线程存活时间,设置 allowCoreThreadTimeOut(true) 也可以让核心线程超时后被回收。
- BlockingQueue:任务队列。可选 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue
- threadFactory:线程工厂。可以指定有业务含义的线程名以及线程内部抛异常时的异常处理器 UncaughtExceptionHandler。
- RejectedExecutionHandler:拒绝策略。CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。
线程池状态和线程数
除了上述7个参数外,线程池使用一个 32 位的整数存放线程池的状态及当前池中的线程数。其中高 3 位存放线程池的状态,低 29 位存放线程数。
补充机组知识:
- 原码、补码、反码
- & | ~ >> << 运算
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 这里 COUNT_BITS 设置为 29(32-3),意味着前三位用于存放线程状态,后29位用于存放线程数
// 很多初学者很喜欢在自己的代码中写很多 29 这种数字,或者某个特殊的字符串,然后分布在各个地方,这是非常糟糕的
private static final int COUNT_BITS = Integer.SIZE - 3;
// 000 11111111111111111111111111111
// 这里得到的是 29 个 1,也就是说线程池的最大线程数是 2^29-1=536870911
// 以我们现在计算机的实际情况,这个数量还是够用的
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 我们说了,线程池的状态存放在高 3 位中
// 运算结果为 111跟29个0:111 00000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// 000 00000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 将整数 c 的低 29 位修改为 0,就得到了线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 将整数 c 的高 3 为修改为 0,就得到了线程池中的线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
// 存放线程池状态和线程数的 32 整数
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* Bit field accessors that don't require unpacking ctl.
* These depend on the bit layout and on workerCount being never negative.
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
这介绍下线程池的状态和状态转换的过程:
- RUNNING:接收新的任务,处理队列中的任务。
- SHUTDOWN:不接收新的任务提交,但会处理已提交到等待队列的任务。
- STOP:不接收新的任务提交,而且不再处理队列中的任务,中断当前正在执行的线程。
- TIDYING:所有任务都销毁了,workCount 为 0。线程池的状态在转 TIDYING 时会调用钩子函数 terminated()。
- TERMINATED:terminated() 方法结束后,线程池状态变成这个。
各个状态的转换过程有如下几种:
- RUNNING -> SHUTDOWN:当调用 shutdown() 后会走这个状态转换。
- RUNNING | SHUTDOWN -> STOP:当调用 shutdownNow() 后会进行这个状态转换。
- SHUTDOWN -> TIDING:等待队列和线程池任务都清空后,进行这个状态转换。
- STOP -> TIDING:当任务队列清空后,进行这个状态转换。
- TIDING -> TERMINATED:当 terminated() 方法执行结束后。
Worker 内部类
线程池中的线程被包装成 Worker,任务是 Runnable(内部变量名叫 task 或 command),线程是 Worker。
Worker 实现了 Runnable 接口,当线程启动会调用 Worker.run 方法,继续调用 ThreadPoolExecutor.runWorker 方法。Worker 还继承了 AQS 用于实现排它锁。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
// 这个是真正的线程,任务靠你啦
final Thread thread;
// 前面说了,这里的 Runnable 是任务。为什么叫 firstTask?因为在创建线程的时候,如果同时指定了
// 这个线程起来以后需要执行的第一个任务,那么第一个任务就是存放在这里的(线程可不止执行这一个任务)
// 当然了,也可以为 null,这样线程起来了,自己到任务队列(BlockingQueue)中取任务(getTask 方法)就行了
Runnable firstTask;
// 用于存放此线程完成的任务数,注意了,这里用了 volatile,保证可见性
volatile long completedTasks;
// Worker 只有这一个构造方法,传入 firstTask,也可以传 null
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 调用 ThreadFactory 来创建一个新的线程,将 worker 自己传给 Thread 的 runnable 属性
this.thread = getThreadFactory().newThread(this);
}
// 这里调用了外部类的 runWorker 方法
public void run() {
runWorker(this);
}
...// 其他几个方法没什么好看的,就是用 AQS 操作,来获取这个线程的执行权,用了独占锁
}
讲过前面的铺垫,进入正文 submit 最终导向的 ThreadPoolExecutor.execute() 方法。
execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 标识线程池状态和线程数的整数
// ctl 是 AutomicInteger 底层的 volatile 保证了可见性
int c = ctl.get();
// 如果当前线程数少于核心线程数,那么直接添加一个worker来执行任务
// 创建一个核心线程,并把当前任务 command 作为这个线程的第一个任务 firstTask
if (workerCountOf(c) < corePoolSize) {
// 添加任务成功,execute 方法就结束了;提交任务嘛,线程池接收任务,这个方法也就可以返回了。
// 而任务的执行则是在 addWorker 构建 Worker 时开启新线程异步执行的。
if (addWorker(command, true))
return;
// 到这里说明 addWorker 失败
c = ctl.get();
}
// 到这里说明【线程数>=corePoolSize】或者【addWorker 失败了】
// 如果线程池是运行状态,任务command 入队任务队列 workQueue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 如果线程池已经不处于 Running 状态那么移除任务 command,并且执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池还在运行中,但线程数为0 那么开启新线程
// 这块代码的意图是:担心任务都提交到队列中了,但线程都关闭了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false)) // 如果 workQueue 已满那么以 maximumPoolSize 为界创建新线程
// 如果失败,说明当前线程数达到 maximumPoolSize,执行拒绝策略
reject(command);
}
- 当前线程数小于 corePoolSize 时,开启一个新线程执行任务
- 当前线程数大于等于 corePoolSize 时,任务进入工作队列 workQueue 等待被执行
- 工作队列满载时,判断当前线程数是否小于 maximumPoolSize,开启非核心线程否则执行拒绝策略。
- 非核心线程在空闲超过 keepAliveTime 时间后被回收,也可以调用 allowCoreThreadTimeOut(true) 设置核心线程超时后被回收
- JDK自带的拒绝策略有 CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。
addWorker
addWorker(Runnable firstTask, boolean core) 方法负责创建工作者线程。
// 第一个参数 firstTask 是准备提交给这个线程执行的任务,可以为 null
// 第二个参数 core 代表要创建的是核心线程还是非核心线程
// true 代表使用核心线程数 corePoolSize 作为创建线程的界限,即如果线程数已经达到 corePoolSize,那么就不能响应这次创建线程的请求
// false 代表使用最大线程数 maximumPoolSize 作为创建线程的界限
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 牢记线程池状态是 SHUTDOWN 时,不允许提交新任务,但允许工作队列中的任务继续执行
// 如果线程池已关闭当满足下面条件之一时,不允许创建 worker:
// 1. 线程池状态 > SHUTDOWN 也就是 STOP、TIDYING 或 TERMINATED
// 2. firstTask != null 即线程池状态是 SHUTDOWN 但还有任务要提交,拒绝创建 worker
// 3. workQueue.isEmpty 即线程池状态是 SHUTDOWN 但工作队列 workQueue 已经空了,拒绝创建 worker
// 多说一句,如果线程池状态是 SHUTDOWN 但 firstTask == null 且 workQueue 非空,是允许创建 worker 的
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 如果 core == true 代表使用 corePoolSize 为界创建 worker 否则使用 maximusPoolSize 为界。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 如果 cas 成功,那么所有创建线程前的条件都已满足,跳出外层 for 循环,准备创建线程执行任务
// 如果 cas 失败,说明有其他线程在尝试往线程池中创建线程,继续判断线程池状态决定走内层还是外层循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 由于有并发,重新再读一下 ctl
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
// 如果线程池状态发生改变,存在其他线程关闭了线程池的可能,从外层 for 循环继续,否则进入内层 for 循环
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 到这里我们认为可以开始创建 worker 了,至少当前是满足所有校验条件的
// worker 是否已经启动
boolean workerStarted = false;
// 是否已将这个 worker 添加到 workers 这个 HashSet 中
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); // 将 firstTask 传给这个 worker,可能是 null
// 取 worker 中的线程对象,Worker 构造方法会调用 ThreadFactory 创建一个线程
// 当前的Worker对象作为线程的 runnable 属性,线程启动时调用当前Woker对象的 run 方法
final Thread t = w.thread;
if (t != null) {
// 这个是整个线程池的全局锁,保证下面「添加Worker到HashSet」「更新历史最大线程数」是线程安全的
// 而关闭一个线程池也需要持有 mainLock 全局锁,至少在 addWorker 持有锁期间,线程池不会被关闭
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 重新获取线程池状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || // 小于 SHUTDOWN 说明是 RUNNING
// 等于 SHUTDOWN 不接收新任务 firstTask==null,但会执行等待队列中的任务
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // worker 里的 thread 可不能是已经启动的
throw new IllegalThreadStateException();
workers.add(w);
// largestPoolSize 用于记录线程池历史最大线程数,因为 workers 是不断增加或减少的
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) { // 添加worker成功的话,启动这个线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) // 如果线程没有启动成功,需要做清理工作。比如前面加1的 workerCount 减1
addWorkerFailed(w);
}
return workerStarted; // 返回线程是否启动成功
}
runWorker
我们知道worker对象的线程启动后,会执行 worker 的 run 方法:
// Worker 类的 run() 方法
public void run() {
runWorker(this);
}
继续往下看 runWorker 方法。
// 此方法由Worker线程启动后调用,使用while循环不断地从等待队列中获取任务并执行
// Worker在初始化时可以指定 firstTask,那么第一个任务也就不需要从workQueue中获取
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();
// 线程池状态大于等于 STOP 意味着当前Worker线程也要中断
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(); // 执行我们自己的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; // 置空 task 准备 getTask 获取下一个任务
w.completedTasks++; // 累加已完成的任务,Worker对象 completedTasks 属性值加一
w.unlock(); // 释放掉 worker 的独占锁
}
}
completedAbruptly = false; // 工作队列为空,当前Worker线程正常退出
} finally {
// 到这里需要执行线程关闭
// 1. 说明 getTask 返回 null,队列中已经没有要执行的任务,执行关闭
// 2. 任务执行 run 方法过程中抛出异常
// 第一种情况,getTask 方法中已经将 workerCount 减一
// 第二种情况,workerCount 并没有进行处理,需要在 processWorkerExit 中将 workerCount 减一
processWorkerExit(w, completedAbruptly);
}
}
我们继续看 getTask() 是怎么获取任务的。
getTask
// 整个方法有三种可能返回 null 来关闭线程:
// 1. 线程池状态大于等于STOP时,返回 null
// 2. 线程池状态等于SHUTDOWN且工作队列 workQueue 已空时,返回 null
// 3. 「线程数大于线程池最大线程数」或者「发生超时时」返回 null
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 1. 线程池状态大于等于STOP时,线程数 workCount--,返回 null
// 2. 线程池状态等于 SHUTDOWN 且工作队列 workQueue 为空时,返回 null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 允许核心线程超时或者线程数 > 核心线程数时,允许超时
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 以下两种情况均返回 null
// 1. 线程数大于线程池最大线程数
// 2. 允许超时且已超时
// 且线程数大于1或者工作队列已空,CAS 线程数减一,成功返回 null,否则继续循环
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ? // 允许超时,调用 poll 方法,超时时间 keepAliveTime
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take(); // 不允许超时,调用 take 方法阻塞等待任务
if (r != null)
return r;
timedOut = true; // 如果超时时间 keepAliveTime 内未成功返回任务,设置超时标识
} catch (InterruptedException retry) {
// 如果此 worker 发生了中断,采取重试策略。
// 至于为什么会发生中断,是因为开发者可以调用 setMaximumPoolSize 调小线程池最大线程数
// 意味着超出的部分线程需要关闭掉,再次进入 for 循环会由于 wc > maximumPoolSize 返回 null 而走关闭线程逻辑
timedOut = false;
}
}
}
到这里基本说完了 execute 方法的执行流程,我们再回到 execute(Runnable command) 方法,看看各个分支。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 标识线程池状态和线程数的整数
// ctl 是 AutomicInteger 底层的 volatile 保证了可见性
int c = ctl.get();
// 如果当前线程数少于核心线程数,那么直接添加一个worker来执行任务
// 创建一个核心线程,并把当前任务 command 作为这个线程的第一个任务 firstTask
if (workerCountOf(c) < corePoolSize) {
// 添加任务成功,execute 方法就结束了;提交任务嘛,线程池接收任务,这个方法也就可以返回了。
// 而任务的执行则是在 addWorker 构建 Worker 时开启新线程异步执行的。
if (addWorker(command, true))
return;
// 到这里说明 addWorker 失败
c = ctl.get();
}
// 到这里说明【线程数>=corePoolSize】或者【addWorker 失败了】
// 如果线程池是运行状态,任务 command 进入任务队列 workQueue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 如果线程池已经不处于 Running 状态那么移除任务 command,并且执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池还在运行中,但线程数为0 那么开启新线程
// 这块代码的意图是:担心任务都提交到队列中了,但线程都关闭了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false)) // 如果 workQueue 已满那么以 maximumPoolSize 为界创建新线程
// 如果失败,说明当前线程数达到 maximumPoolSize,执行拒绝策略
reject(command);
}
拒绝策略
上面 execute 方法有两种情况会调用 reject(command) 方法拒绝任务,此时线程池已经不能接收任务。
final void reject(Runnable command) {
// 执行拒绝策略
handler.rejectedExecution(command, this);
}
- 线程池已经不处于运行状态,对新的执行任务拒绝策略,不再接收新任务。
- 工作队列 workQueue 已满且线程数达到线程池最大线程数 maximumPoolSize,执行拒绝策略。
接下来我们说一说 ThreadPoolExecutor 自带的四种拒绝策略。
// 只要线程池没有被关闭,那么由提交任务的线程自己来执行这个任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
// 不管怎样,直接抛出 RejectedExecutionException 异常
// 这个是默认的策略,如果我们构造线程池的时候不传相应的 handler 的话,那就会指定使用这个
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 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);
}
}
}
Executors 工具类
Executors 提供四类线程池静态构造方法:
-
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }最大线程数设置为与核心线程数相等,此时 keepAliveTime 设置为 0 (这里的 keepAliveTime 即使设置为非0也是没用的,因为默认线程池不会回收核心线程,除非设置 allowCoreThreadTimeOut==true),任务队列使用无界队列 LinkedBlockingQueue 大小是 Integer.MAX_VALUE。
-
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }和 newFixedThreadPool 一样,只要设置核心线程数和最大线程数都是1,线程池中只有一个核心线程。
-
newCachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }缓存线程池:核心线程数是0,需要的时候就创建一个新的线程,同时可以在复用已创建的线程,空闲超时时间是 60 秒,工作队列是 SynchronousQueue 本身不存储任何元素,必须要有任务或者工作者线程,否则阻塞等待。
缓存线程池适用于任务可以较快速完成的情况,当线程空闲 60 秒没获取到任务时,将关闭此线程并从线程池中移除。所以线程池空闲很长时间也没有问题,随着所有县城超时被关闭,整个线程池不会占用任何的资源。
缓存线程池的 execute 提交任务过程分析:corePoolSize == 0 所以任务将直接提交到工作队列 SynchronouseQueue,第一个任务提交时 workQueue.offer 方法肯定会返回 false,因为此时线程池中没有线程。将进入到最后一个分支 addWorker 创建第一个线程。之后再提交任务,将取决于是否有空闲的线程接受任务,有则进入第二个 if 分支,否则代表已创建的线程超时被回收,走最后的 else if 分支创建新线程。
int c = ctl.get(); // corePoolSize 为 0,所以不会进到这个 if 分支 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } // offer 如果有空闲线程刚好可以接收此任务,那么返回 true,否则返回 false 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); } else if (!addWorker(command, false)) reject(command);
总结
回答开篇的面试题
-
Java 线程池有哪些属性?
- corePoolSize:核心线程数。默认情况下核心线程超时不回收,可以调用 setAllowCoreThreadTimeOut 方法回收核心线程。
- maximumPoolSize:线程池最大线程数。线程池允许创建的最大线程数。线程池历史最大线程数可以查看ThreadPoolExecutor 的 largestPoolSize 属性。
- BlockingQueue:任务队列。可选 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue。
- TimeUnit:线程空闲超时时间单位。
- keepAliveTime:线程空闲超时时间大小。
- ThreadFactory:线程工厂,可以通过 Guava 的 ThreadFactoryBuilder 构建 ThreadFactory。
- RejectedExecutionHandler:拒绝策略。可选 CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。
-
提交任务时工作队列 workQueue 满了怎么办?
以线程池最大线程数 maximumPoolSize 为界,创建新线程处理任务,当线程数达到线程池最大线程数时,执行拒绝策略。
-
什么情况下会创建线程?
-
当前线程数小于 corePoolSize 时创建核心线程
-
当前线程数已经达到 corePoolSize 时,任务提交到工作队列 workQueue 等待核心线程取任务。
-
工作队列已满时,以线程池最大线程数 maximumPoolSize 为界创建非核心线程,处理任务;如果线程数达到 maximumPoolSize 执行拒绝策略。
注意工作队列采用 LinkedBlockingQueue 无界队列时,即使线程数达到 corePoolSize 线程数也不会再增长,因为后面的任务入队无界队列即可,此时的 maximumPoolSize 没什么意义。
-
-
提交任务时线程池满了,怎么办?
线程数达到 maximumPoolSize 时执行拒绝策略,ThreadPoolExecutor 自带四种拒绝策略实现类。
- CallerRunsPolicy:只要线程池没有关闭,任务由提交的线程自己来执行
- AbortPolicy:直接抛出 RejectedExecutionException 异常
- DiscardPolicy:忽略当前任务
- DiscardOldestPolicy:将工作队列队头的任务(即等待时间最久的任务)移除,然后提交这个任务到工作队列
-
空闲线程怎么关闭?
先说下空闲线程什么时间触发关闭,当 runWorker 方法通过 while 循环调用 getTask 返回 null 时,关闭当前线程。
那什么时候 getTask 会返回 null 呢?
- 线程池状态大于等于 STOP 时,线程池已关闭。
- 线程池状态等于 SHUTDOWN 且工作队列 workQueue 为空
- 当前线程数大于线程池最大线程数 maximumPoolSize
- 当前的 Worker 线程空闲超时。调用超时 poll 返回 null,在超时时间内未获取到任务。
-
线程池的执行顺序是怎样的?如何让线程池按照 core、max、queue 的执行顺序去执行?
- 当前线程数小于核心线程数 corePoolSize 时创建核心线程
- 当前线程数达到核心线程数时,任务进入工作队列 workQueue,排队等待执行
- 当工作队列已满时,在不超过线程池最大线程数 maximumPoolSize 前提下,创建非核心线程。
- 当线程数达到线程池最大线程数 maximumPoolSize 时,执行拒绝策略。
如何让线程池按照 core、max、queue 的执行顺序执行呢?即调整 2、3 步骤的执行顺序。
通过自定义阻塞队列修改 offer() 方法逻辑,当线程数 < maximumPoolSize 时直接返回 false,execute 方法就会先创建非核心线程数,达到按照 core -> max -> queue 的执行顺序。
-
Executors.newFixedThreadPool(...) 和 Executors.newCachedPool(...) 构造出来的线程池有哪些差异?
- 核心线程数不同。newFixedThreadPool 核心线程数通常大于0,newCachedPool 核心线程数为0
- 最大线程数不同。newFixedThreadPool 最大线程数等于核心线程数,newCachedPool 最大线程数是 Integer.MAX_VALUE
- 工作队列不同。newFixedThreadPool 采用无界队列 LinkedBlockingQueue,newCachedPool 采用 SynchronousQueue
- 超时时间不同。newFixedThreadPool 的超时时间默认不生效,因为除非设置 setAllowCoreThreadTimeOut = true,否则核心线程不会被回收。newCachedPool 默认超时时间 60s,适用于任务处理时间很快的场景,占用资源少。
-
子线程抛出的异常,主线程能感知到吗?任务执行过程中发生异常,怎么处理?
感知不到,在异步执行过程中,子线程抛出的异常应该由子线程自己处理,而不是需要主线程感知来协调处理。
我们可以使用 Guava ThreadFactoryBuilder 指定 UncaughtExceptionHandler(thread, throwable) 这样就可以捕获子线程异常。
如果某个任务执行出现异常,那么执行任务的线程会被关闭而不是接着执行其他的任务。然后会启动一个新的任务替代它。
线程池有 execute、submit 两种提交任务方式,最终都会执行到 ThreadPoolExecutor.runWorker() 方法。而 runWorker 的 task.run() 方法最终会调用到 FutureTask.run() 方法,此方法会将业务方法的异常捕获到,然后调用 setException(ex) 方法。
public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { runner = null; int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } } protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; // 异常信息被保存到 outcome Object 对象中 UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }结论:对于线程池的异常处理有三种处理方式:
- 业务代码内使用 try/catch 捕获异常信息(最推荐)
- 构造线程池时指定 UncaughtExceptionHandler 重写 uncaughtException(thread, throwable) 方法处理子线程异常,通常打印日志
- 重写 ThreadPoolExecutor.runWorker 的 afterExecute(task, thrown) 钩子方法,感知异常细节
-
什么时候会执行拒绝策略?
- 线程池状态大于等于 SHUTDOWN,移除已入队的任务,并执行拒绝策略。
- 线程池在运行中,但线程数达到最大线程数 maximumPoolSize,执行拒绝策略。
-
任务执行结果是怎么获取的?流程是怎样的?
通过线程池 submit 方法提交的任务,调用 Future 的 get 方法获取执行结果,建议使用带超时参数的 get 方法。最终会调用 FutureTask.get 方法
// FutureTask.java 198 public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (unit == null) throw new NullPointerException(); int s = state; if (s <= COMPLETING && // 超时等待任务完成 (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING) throw new TimeoutException(); return report(s); // 获取任务执行结果或异常信息 } // FutureTask.java 116 private V report(int s) throws ExecutionException { Object x = outcome; // 将 outCome 保存的结果或者异常信息返回 if (s == NORMAL) return (V)x; if (s >= CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x); }