ThreadPoolExecutor 分析
1. 继承关系
从下面的图可以看出,根就是Executor
接口,下面就挨个来看看这些接口和抽象类干了什么样的事情。
Executor接口
这个接口用于执行一个任务,不要求这个任务同步,异步,任务怎么执行很宽泛。
execute
方法用于执行任务,如果任务不能执行,抛出RejectedExecutionException
,如果任务为空,抛出NullPointerException
,这两个异常都是非检查型异常,不需要强制声明。
public interface Executor {
void execute(Runnable command);
}
ExecutorService接口
ExecutorService继承于Executor,在之前的基础上,增加了几个方法,主要是提供了管理终端,这些方法可以为一个或者多个异步任务生成对于的future对象。
ExecutorService
可以关闭,关闭之后就不能继续接受新的任务了,有两个关闭的方法
-
shutdown
在终止之前,会将之前提交的任务运行完。不会接受新任务
-
shutdownNow
不会接受新任务,会尝试中断现在正在执行的任务
并且,在之前继承上,新增了一个submit
方法,submit方法是基于Executor.execute(Runnable)
方法创建的,他可以返回一个future,用于任务的取消和或者等待任务完成。
invokeAny
和invokeAll
方法用于批量执行任务。
Executors
方法提供了工厂方法来创建ExecutorService
package java.util.concurrent;
import java.util.List;
import java.util.Collection;
public interface ExecutorService extends Executor {
//执行前面提交的任务,但不会接受新任务。 如果已经关闭,多次调用没有啥问题。
//这个方法不等待先前提交的任务完成执行。 使用 awaitTermination 来做到这一点。
void shutdown();
// 中断所有正在执行的任务,并且返回等待执行任务的集合
// 这个方法不会等待主动执行的任务终止。 使用 awaitTermination 来做到这一点。
List<Runnable> shutdownNow();
boolean isShutdown();
// 如果关闭后所有任务都已完成,则返回 true。
//必须先调用 shutdown 或 shutdownNow,否则isTerminated 不可能为真。
boolean isTerminated();
// 阻塞直到所有任务在关闭请求后完成执行,或发生超时,或当前线程被中断,按照先发送的来
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
//下面的提交方式,这个提交方式和future的一样,一个Runnable,在来一个T,操作一拨,直接返回
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<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;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
AbstractExecutorService抽象类
ExecutorService的默认实现,重点关注 submit
方法,这个方法将传递进来的方法分装为Future,交给execute
方法执行。这里分装Future对象的操作和之前Future实现分析里面逻辑一样,复用了那一块的代码。
package java.util.concurrent;
import java.util.*;
/**
ExecutorService的默认实现
*/
public abstract class AbstractExecutorService implements ExecutorService {
// 将提交的Runnable封装为一个FutureTask接口。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
//下面是也是callable的封装
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//提交任务,空指针检查,构建新的task,调用execute方法执行任务。execute是给具体的类来实现的。
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();
// 和上面的不一样,newTaskFor只是封装了不同的对象
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;
}
// 这个方法很简单,只要提交的任务里面一个完成就好了,
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();
// 构建futures集合
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
// 装饰者模式。将当前的执行器利用ExecutorCompletionService包装。
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
try {
// 其实就是循环调用一个任务,重写了FutureTask,在任务完成之后将任务放在一个queue中,如果queue中有一个,就完成了,直接返回就行
ExecutionException ee = null;
//这不用说了,经典的等待超时的写法
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Iterator<? extends Callable<T>> it = tasks.iterator();
// 提交一个任务,将任务封装为QueueingFuture,注意这里是只是提交第一个集合中的第一个任务
futures.add(ecs.submit(it.next()));
// ntasks一开始的值是任务的数量,现在ntasks代表的意思就是还没有执行的任务
--ntasks;
// 现在提交的任务中执行的任务数量为1
int active = 1;
// 死循环开始
for (;;) {
// 从queue中出队
Future<T> f = ecs.poll();
// 没有任务就在提交一个任务
if (f == null) {
//如果还有没有执行的任务
if (ntasks > 0) {
// 能执行的任务数--
--ntasks;
//继续提交一个任务
futures.add(ecs.submit(it.next()));
//活动的数量++
++active;
}
// 如果没有活动的任务,跳出循环
else if (active == 0)
break;
//如果需要 ntasks<=0了,说明所有的任务都已经提交了,那就只能等待了,不能提交了
else if (timed) {
//等待,
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
// 如果唤醒之后还是没有,就抛异常
if (f == null)
throw new TimeoutException();
// 延时等待
nanos = deadline - System.nanoTime();
}
// 如果不需要超时等待,就只能无限期的take
else
f = ecs.take();
}
// 如果有任务,就直接get就行,
if (f != null) {
--active;
try {
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
// 在完成之后,将所有的任务都取消掉
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).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));
}
//循环所有的任务,循环提交任务,将Future放在一个集合里面,循环future集合,等待或者返回
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f);
}
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
try {
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
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 {
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.
for (int i = 0; i < size; i++) {
execute((Runnable)futures.get(i));
nanos = deadline - System.nanoTime();
if (nanos <= 0L)
return futures;
}
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();
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
}
2. ThreadPoolExecutor分析(重点是execute方法分析)
线程池解决了两个问题
- 执行大量异步任务的性能提升
- 对任务的管理
Executors
提供了工厂方法,提供了几个线程池
- Executors.newCachedThreadPool
- Executors.newFixedThreadPool
- Executors.newSingleThreadExecutor
- Executors.newWorkStealingPool
- Executors.newSingleThreadScheduledExecutor
- Executors.newScheduledThreadPool
注意:这里不会讲解上面的线程池是什么意思,怎么用,这是一篇从源码分析,线程池执行任务。要想了解具体的用法,这篇文章不合适
自定义线程池的几个重要的点
Core(核心线程数) and maximum pool (最大线程数) sizes
ThreadPoolExecutor 会根据 corePoolSize和 maximumPoolSize 的值来设置线程池中线程的大小。 提交新任务,如果运行的的线程少于 corePoolSize 时,即使其他工作线程空闲,也会创建一个新线程来处理请求,知道超过了corePoolSize。
如果线程数超过 corePoolSize 但小于 maximumPoolSize,只有在队列已满时才会创建新线程(一直创建到maximumPoolSize)。 如果corePoolSize和maximumPoolSize一样,就会创建一个大小固定的线程池。
corePoolSize和maximumPoolSize 只能通过构造方法或者
setCorePoolSize, setMaximumPoolSize
设置。但是后者可以动态的更改线程池的大小提前创建线程
默认线程是任务来的时候(提交任务)才会创建,来一个,创建一个,但是可以提前创建corePoolSize和maximumPoolSize的线程数
prestartCoreThread
prestartAllCoreThreads
创建线程
创建线程是通过
ThreadFactory
创建,默认是Executors.defaultThreadFactory
,默认创建的线程都是同一个ThreadGroup,同一个优先级,都不是守护线程,通过ThreadFactory
接口,可以修改线程的名字,线程组,线程优先级,是否是守护线程等等,ThreadFactory
接口如果返回了一个null,线程池不会报错,但是不会执行任务。Keep-alive times
线程池中线程数量超过 corePoolSize ,如果空闲时间超过 keepAliveTime,超出
corePoolSize
的线程将被终止。可以使用setKeepAliveTime(long, TimeUnit)
方法动态更改此参数。 使用 Long.MAX_VALUE TimeUnit.NANOOSECONDS 值可以有效地禁止空闲线程在关闭之前终止。 默认情况下,这个策略仅在线程数超过 corePoolSize 有作用。但是``allowCoreThreadTimeOut(boolean) `方法,会尝试中断所有空闲的线程。
Queuing
任何实现了
BlockingQueue
接口的方法都可以
- 如果线程的数量小于
corePoolSize
,每次提交任务都是创建新的线程- 超过corePoolSize,任务会提交到
BlockingQueue
BlockingQueue
放不下,创建线程数到maximumPoolSize
- 超过
maximumPoolSize
,任务就会被拒绝Rejected tasks
Executor 已经关闭,或者 Executor 的任务已经饱和的时候,继续提交任务就会被拒绝。
在满了之后,调用
RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)
方法。有四个默认的拒绝策略
- 默认的是
ThreadPoolExecutor.AbortPolicy
,拒绝的时候抛出 RejectedExecutionException。ThreadPoolExecutor.CallerRunsPolicy
,让调用者自身执行任务,可以减慢提交新任务的速度。ThreadPoolExecutor.DiscardPolicy,
,啥都不干,这是简单的丢掉。ThreadPoolExecutor.DiscardOldestPolicy
,如果executor没有关闭,工作队列头部的任务就会被丢弃,然后通过executor来重新调用execute方法提交任务。Hook methods
子类可以重写
beforeExecute(Thread, Runnable)
和afterExecute(Runnable, Throwable)
方法来在执行任务之前做操作,比如ThreadLocal的操作,日志的打印。队列的维护
通过
getQueue()
方法能获取到任务队列。通过任务队列就可以获取当前有多少任务在执行和队列的信息终止
程序中不再引用线程池,并且池中没有剩余线程,线程池将自动关闭。如果您想确保即使用户忘记调用 shutdown 也能回收未引用的池,设置TTL,并且调用
allowCoreThreadTimeOut(boolean)
方法。这样就可以在没有shutDown的方法下面,也是可以关掉的。
ctl的说明
ctl是一个原子整数,在一个字段里面封装了两个字段
- workerCount,表示有效线程数
- runState,表示线程池的状态,包括正在运行,正在关闭等等。
因为将两个字段融为一个int类型了, 所以将 workerCount 限制为 (2^29)-1,而不是 (2^31)-1。这可能大概是一个问题,如果workerCount超过就不太好了,只能将int变为AtomicLong,并且调整移位/掩码。但是,int比AtomicLong快。
runState 主要提供了线程池的生命周期控制,值域有
RUNNING:接受新任务并处理队列里面的任务
SHUTDOWN:不接受新任务,但处理队列里面的任务
STOP:不接受新任务,不处理队列里面的任务, 并中断正在进行的任务
TIDYING:所有任务都已终止,workerCount 为零,转换到状态 TIDYING 的线程将运行 terminate() 钩子方法
TERMINATED: terminate() 已完成
runState上面的取值的几个状态,会随着时间增加,但不会命中所有的状态,也就是说,线程池的状态,只会从running开始,一直增加,不会退回去,并且,上面的状态都不需要全部都走一遍。
几个状态的转化如下图所示
Worker类说明
从继承和实现的关系可以看到,继承与AQS,并且实现了Runnable接口。
问题:
为什么要继承于AQS?继承AQS,这是可重入的吗?
继承与AQS是为了在执行任务的时候,保证不会被打扰。
不是可重入锁。0代表没有锁,1代表添加了锁,-1表示初始状态
- 为什么要实现Runnable接口。
Runnable接口就是一个代理方法,将自己作为new thread 的参数传递进去,在线程运行的时候,通过Worker里面的run方法调用到
runWorker(this)
方法,来真正的执行任务。
关于属性的解释如下
- thread:线程的引用,如果ThreadFactory创建线程失败,这就是个null
- firstTask:这个worker的第一个任务。
- completedTasks:每个线程完成的任务数量
关于AQS加锁和释放锁的操作就不在这里说了,里面的代码也很简单,重点看一下interruptIfStarted
方法。
在这个方法里面,只要这个worker启动了,并且worker里面的Thread没有中断过,就会调用work里面的Thread的interrupt
方法。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
runWorker方法分析
真正执行task的地方,while循环,一直从队列中获取task或者从worker的firstTask中获取task,判断线程池的状态,利用try catch来做逻辑控制,利用标志位来区分worker是否是正常结束,在执行方法之前和之后调用hook方法。
问题
在开始的时候为啥获取
Thread.currentThread()
,而不是worker里面的Thread属性这俩其实是一样的,runWorker方法是在Worker类的run方法里面调用的,而Thread属性就是通过ThreadFactory创建的,并且在创建的时候将Worker传递进去了,这说明这俩是一致的。
Worker里面的firstTask能否再次赋值
不能,只有在创建Worker的时候才会赋值,此外,在运行的时候,只能通过从任务队列中获取task
task异常之后,这个task算完成吗?
算,在finally里面的代码肯定是要运行的。
什么样的情况下,会跳出while循环
有两种情况,正常和异常
- 异常,task异常就会先捕获原始异常,然后再次会抛出。利用try catch 来打破循环
- 正常结束,如果从任务队列中没有获取到task,就会正常的结束。退出循环。(超过核心线程的线程数的回收)
// 需要注意,
final void runWorker(Worker w) {
// 当前线程,为啥不是w里面的thread
Thread wt = Thread.currentThread();
//拿到worker的firstTask的引用,将worker里面的task变为null
// 拿到task就是运行的任务的引用
Runnable task = w.firstTask;
w.firstTask = null;
// 一上来先解锁,意味着从这里之后可以用线程来获取锁。只要拿到锁,别的就不能获取到锁了。
w.unlock();
//突然完成的标志位,这个标志位有啥意思。
boolean completedAbruptly = true;
try {
// 一直从task开始循环,一直从队列中获取值。
while (task != null || (task = getTask()) != null) {
// 获取锁,意味着这个任务只能这个work运行,并且work运行的时候,不会被别的work中断。
w.lock();
//如果线程池的状态大于stop,或者当前线程中断中断了,就将当前线程中断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//执行前的回调方法,要注意这里的try catch的逻辑
// 利用try catch来实现逻辑。很巧妙
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 {
//执行之后的回调方法,写在finally里面肯定会执行的。
afterExecute(task, thrown);
}
} finally {
// 将task变为null,work完成的task数量++。解锁,表示这个任务已经执行结束。
task = null;
w.completedTasks++;
w.unlock();
}
// 正常执行任务(任务不会出错) while循环到这里就结束了,一旦出现了异常,就会利用try catch机制打破while循环
// 直接到下面的finally,
}
// completedAbruptly表示,当前的worker是否是突然终止的(异常终止)
// 如果take没有获取到任务,那么这个worker就是正常的退出。(比如非核心线程回收中断操作,这种就是正常的结束)
completedAbruptly = false;
} finally {
// 处理线程退出、用completedAbruptly来区分是否是突然中断或者正常中断
processWorkerExit(w, completedAbruptly);
}
}
getTask方法分析
从任务队列中获取task这里会有TTL机制。
这里用到了allowCoreThreadTimeOut,从这里可以看出,超时等待是要在整个queue中等待超时。也就是在队列中任务处理完成之后,在经历TTL之后还是没有获取到任务,超过核心线程数的work就会被销毁。
问题
timedOut和timed,在死循环里面的角色是啥?
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; }
这里的代码会在什么样的情形下面起作用。
没有看懂这里的目的
private Runnable getTask() {
boolean timedOut = false; // 等待超时标志
//一开始死循环冲起来,
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果线程池的状态大于等于SHUTDOWN,并且状态大于stop或者队列是空的,说明不能获取任务,
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//allowCoreThreadTimeOut又出现了,是否运行所有的线程都适用TTL,
// timed等待超时标志位,如果allowCoreThreadTimeOut不为true,则只有大于corePoolSize的时候才会为true,表示需要等待
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//(如果当前的workCount的数量大于 maximumPoolSize || 等待并且等待成功。) &&
// (workCount > 1 || 队列是空的)
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 是否需要等待,如果需要的话,就利用keepAliveTime。,等待超时。不需要就一直等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
processWorkerExit方法分析
处理worker线程退出之后的操作,将需要退出的worker移出worker的集合。调用tryTerminate
方法,然后判断,移除这个worker之后,是否要添加新的worker。这里分为两种情况:
- worker是正常退出的,通过allowCoreThreadTimeOut来确定池中应该存在的最小的worker,如果小于就创建。
- worker是异常退出的。创建一个新的worker。
注意,此出,worker的firstTask是null。
下面有tryTerminate()
方法分析。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果是异常中断的worker,就需要减少worker的数量
if (completedAbruptly)
decrementWorkerCount();
// 拿到mainLock,因为要对worker的集合操作,所以要获取mainLock
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// completedTaskCount是ThreadPoolExecutor里面的一个属性,表示完成的task的数量
completedTaskCount += w.completedTasks;
// 将需要退出的worker从集合中移除
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
// 运行状态比stop小。
// 比stop小的就是RUNNING和SHUTDOWN状态
if (runStateLessThan(c, STOP)) {
//如果不是异常结束的worker
if (!completedAbruptly) {
// min表示最小的应该存活最小的worker。
// 如果设置了allowCoreThreadTimeOut为true,超时时间(TTL)适用于核心线程
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果最小是0 但是workQueue不是空白的
if (min == 0 && ! workQueue.isEmpty())
// 最小的变为1
min = 1;
if (workerCountOf(c) >= min)
// 如果现在存活的worker大于等于min,就不会创建新的worker。
return;
}
// 到这里就要创建worker
// 创建新的worker有两个机会
// 1. 异常,就会创建新的worker代替,之前的任务就结束了
// 2. 正常结束,会判断池中存在的worker如果小于min,就会创建新的worker来代替它。
addWorker(null, false);
}
}
tryTerminate方法分析
一开始就死亡循环。在三种情况下(线程池在运行,状态大于等于TIDYING,状态等于SHUTDOWN 并且 任务队列不是空的),是不会终止的。如果当前的workerCount不是0,只会中断一个空闲的worker。如果workerCount是0,会先把状态变为TIDYING,调用hook方法。最后将状态变为TERMINATED,在一切的最后,唤醒termination的线程。
问题 ?
tryTerminate看起来是和线程池的状态有关系的,但是这里为啥count不是0的时候每次都是终止一个,什么目的?
从下面的代码中可以看到一共有下面几个地方调用了它。
- processWorkerExit(线程退出)
- addWorkerFailed(启动work失败)
- shutdownNow
- shutdown
- remove
在不同的场景下面都能调用这个方法,说明这个方法兼顾的场景多。
这方法就是尝试终止线程池,按照判断逻辑一点点的来说
// 看这里的代码要对照这上面画的图来说 // 线程池在正常运行 if (isRunning(c) || //这意思就是说,线程池已经到垂死挣扎的时刻了,因为TIDYING状态按照上面的图来说,只有满足条件才可以。 // 并且TIDYING方法,只有在这个方法里面设置,如果现在的状态比这个大,那就说明可能这个时刻有线程已经操作了。 runStateAtLeast(c, TIDYING) || // 状态等于SHUTDOWN 并且 任务队列不是空的 // 这个条件就是调用了shutDown方法,如果队列不是空的,说明还有任务,按照shutdown方法的要求,不能关闭线程池, (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) // 上面的三种都不是符合条件的,不符合条件的直接返回。 return;
只要有一个满足,就表示当前的线程池是不能被终止的。
能满足终止条件的就是stop状态和状态为SHUTDOWN但是队列是空的。
这两个状态都是可以终止线程池的,shutdownNow方法本就是这个意思,状态为SHUTDOWN但是队列是空的。也符合shutDown方法的意思。
这里只中断一条我没有看懂。之后在补充补充
// workCount的数量不是0,这里就只中断一条。 if (workerCountOf(c) != 0) { interruptIdleWorkers(ONLY_ONE); return; }
后面的就是调用钩子方法了。没有啥意思了
什么样的情况下,workCount会变为0。
我能想到的,就是在worker是异常中断的,并且此前只有这一个。在上面的 processWorkerExit方法(减少,–)之后,就会变为0。
final void tryTerminate() {
// 一开始就死循环
for (;;) {
int c = ctl.get();
// 如果线程池在正常运行
if (isRunning(c) ||
//这意思就是说,线程池已经到垂死挣扎的时刻了,因为TIDYING状态按照上面的图来说,只有满足条件才可以。
// 并且TIDYING方法,只有在这个方法里面设置,如果现在的状态比这个大,那就说明可能这个时刻有线程已经操作了。
runStateAtLeast(c, TIDYING) ||
// 状态等于SHUTDOWN 并且 任务队列不是空的
// 这个条件就是调用了shutDown方法,如果队列不是空的,说明还有任务,按照shutdown方法的要求,不能关闭线程池,
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
// 上面的三种都不是符合条件的,不符合条件的直接返回。
return;
// 满足条件的就是STOP状态
// 当前活动的worker不是0,就会中断一条(ONLY_ONE默认为true表示一条,false为全部)空闲的worker。怎么区分是否空闲,只要try lock获取成功就说明是空闲的。
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
// 到下面说明 现在池中workcount的数量是0.
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 将线程池的状态变为一个中间状态。TIDYING,
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 钩子方法
terminated();
} finally {
//在finally里面将状态变为TERMINATED。
ctl.set(ctlOf(TERMINATED, 0));
// 唤醒之前等待的线程,(主要就是调用awaitTermination方法的线程)
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
interruptIdleWorkers方法分析
这里的逻辑很简单了,循环判断,只要worker里面的thread没有中断过,并且worker也可以tryLock
成功。就说明当前的worker是空闲的,随即调用线程的interrupt
方法
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
通过上面的分析,已经知道worker就是一个Runnable,并且worker里面有一个Thread,那么worker是在哪里运行的呢?
详情看下面addWorker章节
addWorker方法分析
创建worker对象,并且启动
通过上面worker类的分析知道,创建worker的时候必须给一个task,作为worker里面的firstTask。
在创建worker之前,还是先来一波状态检查,还有worker数的检查,利用自旋消除锁,创建worker对象,添加到worker集合里面,有必要就替换largestPoolSize,并且启动worker,如果添加失败,就调用addWorkerFailed
方法。
问题
判断线程池状态的逻辑是什么?为什么要这么判断
再次看一下这一块的判断逻辑
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false;
运行状态大于等于SHUTDOWN并且rs不是SHUTDOWN或者firstTask不是null或者任务队列是空的。在这样的前提下面就不能创建worker了。
- 如果rs小于SHUTDOWN,那只有running状态了,说明线程池现在是一个合格的状态。可以直接添加worker。如果状态是running,判断的第一个条件都不会满足。
- rs大于等于SHUTDOWN,说明线程池已经不是正常的状态了,如果这个时候状态不是SHUTDOWN,就说明线程池这个时候已经到清理资源的状态了,肯定不能创建了。
- 如果状态是SHUTDOWN,还在提交任务,按照
shutdowm
方法的作用(不在接受新的任务)。这个时候就不在创建worker来处理新提交的任务了。- 如果这个状态还是SHUTDOWN,提交的任务是null,队列里面是空的。说明就不需要创建新的worker了,如果队列里面不是空的。就可以创建新的worker。也就是说,在调用了
shudown
方法之后,线程池的状态变为了shutdown,但是此前任务队列里面还有任务,每次提交一个空任务的时候,就像正常的逻辑一样,条件满足就可以创建新的worker。在创建worker之后,这里的线程池的状态判断是为了什么?
再次检查是为了在此期间,有的线程将线程池的状态变为shutdown方法。因为ctl是一个原子引用。里面用
volatile
修饰,能保证内存的可见性。启动失败,为啥不用catch处理,在这里直接用的finally?
首先,线程异常,jvm会调用
UncaughtExceptionHandler#uncaughtException
方法,不用catch,可能是为了简单吧。因为有标志位,所以,catch里面的处理逻辑就可以放在finally里面,这样看起来简单,大方。
addWorkerFailed方法请看下一章节
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// runState
int rs = runStateOf(c);
// 检查能否创建worker
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//workCount
int wc = workerCountOf(c);
//如果workCount 大于 最大容量 或者大于了corePoolSize或者maximumPoolSize(通过core参数指定)
// 表示不能创建新的worker了
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// workCount ++。跳出retry标志位,也就是跳出这个循环体,
// 如果这次操作失败,说明,同一时刻有线程也在做同样的操作,ctl是原子引用类,里面有
// volatile修饰的变量,能保证内存的可见性。如果再次读取的状态不一致再次循环,循环的开始点是retry标志位。
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
}
}
// 执行到这里,说明workercount已经++成功。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建Worker,将task传递过去,并且在Worker里面,会通过ThreadFactory来创建新的线程。
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//获取mainLock
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//持有锁的前提下面,再次检查runState,防止在获取锁之后,线程池的状态发生变化
int rs = runStateOf(ctl.get());
//如果runState是running,(只有running小于SHUTDOWN)
// 或者 runState是SHUTDOWN并且firstTask是null,
// 这两种情况都是符合要求的。就可以将创建的worker添加到一个集合里面。
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 预检查线程的状态,如果在线程没有启动之前,isAlive方法返回true,就说明出现问题了,还没有启动线程,但这个方法确返回了true。
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
// 如果当前worker集合的个数值,大于largestPoolSize,就赋值替换。
// largestPoolSize表示线程池中有过的worker的最大容量。
if (s > largestPoolSize)
largestPoolSize = s;
// 添加线程成功成功。将workerAdded变为true。
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果添加成功,就启动。这个时候,worker就开始干活了。
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 如果启动失败。也就是workerStarted是false。
if (! workerStarted)
// 就会到下面的这个方法里面,
addWorkerFailed(w);
}
// 返回workerStarted。表示worker是否启动成功。
return workerStarted;
}
addWorkerFailed方法分析
处理启动Worker失败,从worker的集合中移除,如果这个worker存在,减少worker的数量,并且调用 `tryTerminate()方法,重新检查终止,防止因为这个worker的存在,而导致不能终止。
注意,这里调用tryTerminate
方法
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
说到这里,只是说了线程池里面的一些重要的方法,下面就从常见的execute
方法说起。
execute方法分析
从这个方法,可以看出线程池处理任务的大体的步骤
- 如果现在运行的线程数小于
corePoolSize
,尝试启动一个新的worker,将task作为他的firstTask。这个操作是调用addWorker方法(上一章节分析过,他会自动检查runState和workCount,如果不能启动就会返回false) - 上面的条件不满足,task就会入队,如果成功。还会再次检查是否还要添加thread,因为自从上次检查之后,期间在没有检查过,因为第一步和第二部不是原子性的,可能存在在这个期间,线程池的状态变化,或者线程的死亡。还得检查线程池的状态。
- 上面的条件不满足,尝试再次调用addWorker方法。如果也失败了,线程池可能关闭了,或者已经到饱和状态了,调用拒绝策略。
问题
第二步骤的判断有必要吗?
有,第一步和第二步不是一个原子操作,状态完全有可能在这个期间被修改,并且通过上面的分析,addWorker方法,在结束之后就会启动线程,完全有可能在这个期间,线程也出现了问题。我突然想到了一个问题,在线程池运行的时候如果一个线程异常了,就会将这个线程先清除掉,然后创建一个新的线程。那么有没有可能这两个的值在同一时刻读取的是0,然后同时操作。不可能,因为ctl是原子整形类。
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,确保是最新的。
c = ctl.get();
}
// 第二步。
// 判断状态,入队,入队成功后,判断是否还要在添加thread。
if (isRunning(c) && workQueue.offer(command)) {
//再次检查
int recheck = ctl.get();
// 如果线程池的状态不是running,并且从任务队列移除task成功,调用拒绝策略。
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果workCount的数量为0,重新创建一个新的worker。fistTask是null,这个worker也不是核心线程。
// 如果在核心线程满了之后,因为线程池的状态不是running,并且队列不在任务队列中,此时,workcount突然变为0,就会创建一个非核心线程。或者,线程池的状态是running,但是workcount变为了0,也会创建一个非核心线程。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 创建非核心线程失败。对应第三部。
else if (!addWorker(command, false))
reject(command);
}
remove方法分析
注意,这里也调用了tryTerminate方法,这方法只要就是将task从任务队列中移除,并且调用 tryTerminate 方法
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
reject方法分析
调用拒绝策略
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
shutdown方法分析
关闭线程池,之前提交的任务还在运行,但是不会创建处理新提交的任务
启动有序关闭,其中执行先前提交的任务,但不会接受新任务。 如果已经关闭,调用没有额外的效果。
注意,这里也调用了tryTerminate方法
注意,这里也调用了
说明:
- interruptIdleWorkers方法前面已经介绍过了,这里不在累赘说
- 这个方法将线程池的状态变为SHUTDOWN,在想想之前的addWorker方法这对于SHUTDOWN状态的判断,将两个联想起来就很容易理解
public void shutdown() {
// 获取mainLock
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍历检查权限
checkShutdownAccess();
//将线程池的状态变为SHUTDOWN,
advanceRunState(SHUTDOWN);
//中断空闲的线程
interruptIdleWorkers();
// 留给ScheduledThreadPoolExecutor的hook方法
onShutdown();
} finally {
mainLock.unlock();
}
// 再次调用了tryTerminate方法
tryTerminate();
}
shutdownNow方法分析
立刻关闭线程池,并且中断所有正在执行的worker,将队列中没有执行的task返回
说明:
- 这里也得和addWorker方法关联起来,才好理解。重点就是里面对于线程池状态的判断。
注意,这里也调用了tryTerminate方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 还是一样的步骤,只不过这里的状态直接变为STOP了
checkShutdownAccess();
advanceRunState(STOP);
// 中断所有的worker
interruptWorkers();
// 将队列中现存的task返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 调用 tryTerminate方法
tryTerminate();
return tasks;
}
drainQueue方法分析
调用queue的drainto方法,这个方法会将所有可用的元素从queue中移除,并且添加到taskList里面。
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
awaitTermination方法分析
在线程池完全终止之后,调用才能从这个方法返回,也就是说,调用这个方法之后,会一直等待线程池中的任务执行结束,状态变为TERMINATED
先获取mainLock,判断当前线程池的状态是否是TERMINATED,如果是就直接返回,否则就等待,等待在termination里面,termination
是mainLock的Condition。在tryTerminate
方法里面会再次唤醒。(当调用完了terminated
方法之后,在finally里面状态变为TERMINATED,workCount变为0,之后,就会唤醒termination
)
注意,这里也和tryTerminate方法有关系
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
下面看几个set方法,这些方法还挺有意思的,可以动态的调整线程池的大小。
setCorePoolSize方法分析
设置corePoolSize
这个方法比下面的setMaximumPoolSize
方法复杂,因为线程池的机制就是要保证corePoolSize数量的线程数。
corePoolSize分为两种情况,
-
比原来的小
中断空闲的workerCount。
-
比原来的大
在相差多少和queue的size中选择一个最小的,循环添加。
问题?
如果设置的corePoolSize比之前的小,中断空闲线程的时候没有空闲的线程,那这次设置corePoolSize是不是无效?
不是,中断的时候虽然没有空闲的线程,但是修改了corePoolSize,超过的那部分线程,就变为了非核心线程,在
getTask
方法中,会有对应的TTL(keepAliveTime)起作用。Math.min(delta, workQueue.size()) 这段代码有必要吗?为啥不能直接创建delta个线程
因为这个时候不能确定,到底要创建多少个线程,因为,如果直接创建delta个线程,结果队列中有小于delta个任务,多创建的线程不就浪费了吗?所以,这里取两个的最小值。当队列没有值的时候,就不需要创建了。
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
//
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
// 如果当前的的workCount 大于 要设置的corePooSize,就会中断空闲的worker,
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
// 大于0 说明这次设置的corePoolSize比上一次的大
else if (delta > 0) {
// 从队列和delta中选择一个最小的。这样做是为了什么
int k = Math.min(delta, workQueue.size());
// 一直添加
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
setMaximumPoolSize方法分析
设置MaximumPoolSize大小
先赋值,如果超过就中断空闲的线程。
问题
还是和上面同样的问题,如果没有空闲的线程,那么线程池中线程数还一直是之前的数量吗?
不是,和上面的逻辑一样,在
getTask
方法里面会有TTL(KeepAliveTime)
设置
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
this.maximumPoolSize = maximumPoolSize;
if (workerCountOf(ctl.get()) > maximumPoolSize)
interruptIdleWorkers();
}
setKeepAliveTime方法分析
设置KeepAliveTime
public void setKeepAliveTime(long time, TimeUnit unit) {
if (time < 0)
throw new IllegalArgumentException();
if (time == 0 && allowsCoreThreadTimeOut())
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
long keepAliveTime = unit.toNanos(time);
long delta = keepAliveTime - this.keepAliveTime;
this.keepAliveTime = keepAliveTime;
if (delta < 0)
interruptIdleWorkers();
}
那看完这些,线程池中的线程如果出现了异常会怎么办?看完这篇文章,应该有答案了(答案在processWorkerExit方法里面)
关于ThreadPoolExecutor的分析就分析到这里了。 如有不正确的地方,欢迎指出。谢谢。