文章目录
ThreadPoolExecutor源码分析
线程池是可以控制线程创建、释放,并通过某种策略尝试复用线程去执行任务的一种管理框架,从而实现线程资源与任务之间的一种平衡。以下源码基于JDK1.8.
首先来讲一下线程池的优点:
- 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的损耗。
- 提高效应速度,当任务到达时,任务可以不需要等到线程创建就能够立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
1. ThreadPoolExecutor架构
首先来看一下ThreadPoolExecutor的继承关系类图:
1.1 Executor
执行者接口
public interface Executor {
void execute(Runnable var1);
}
Executor是一个顶层接口类,提供了execute
抽象方法。
作用是: 可以用来执行已经提交的Runnable任务对象。
1.2. ExecutorService
ExecutorService是执行者服务接口,可以说是真正的线程池接口了, 在Executor接口的基础上做了拓展。
- 管理任务如何终止的shutdown相关方法
public interface ExecutorService extends Executor {
//启动一次有序的关闭,之前提交的任务执行,但是不会执行新的任务。
void shutdown();
//试图停止正在执行的任务,暂停处理正在等待的任务, 返回一个等待执行的任务列表
List<Runnable> shutdownNow();
//判断线程池是否关闭
boolean isShutdown();
//判断线程池是否处于终止状态。如果shutdown后所有任务都执行完毕,返回true
boolean isTerminated();
//在一个shutdown请求后 阻塞地等待所有任务执行完毕,或者到达超时时间,或者被中断
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
}
- 可以生产用于追踪一个或者多个异步任务执行结果的Future对象的submit()相关方法
//提交一个可执行任务,返回一个Future代表这个任务,等到任务执行完毕,Future#get()方法会返回null.
Future<?> submit(Runnable task);
//提交一个可执行的任务,返回一个Future代表这个任务,等到任务执行完毕,Future#get()方法会返回给定的result
<T> Future<T> submit(Runnable task, T result);
//提交一个可执行的任务,返回一个Future代表这个任务,等到任务执行完毕,Future#get()方法会返回任务执行的结果
<T> Future<T> submit(Callable<T> task);
1.3. ThreadPoolExecutor
1.3.1. 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
我们来看一下线程池的相关构造参数:
- corePoolSize(线程池的基本大小): 当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲线程能够执行新任务也会创建,等到需要执行的任务大于线程池基本大小时就不再创建了,如果调用了线程池的
prestartAllCoreThreads()
方法,线程池会提前创建并启动所有核心线程。 - runableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列,有以下几种阻塞队列可选:
- ArrayBlockingQueue:基于数组的有界阻塞队列
- LinkedBlockingQueue:基于链表的FIFO阻塞队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE。吞吐量通常大于ArrayBlockingQueue。Executors.newFixedThreadPool使用该队列。
- SynchronousQueue:一个不存储元素的阻塞队列,每个插入必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞队列,吞吐量通常大于LinkedBlockingQueue。 Executors.newCachedThreadPool使用该队列。
- PriprityBlockingQueue: 一个具有优先级的无限阻塞队列。
- maximunPoolSize(线程池最大数量): 线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。使用无界的任务队列该参数没有什么效果。
- ThreadFactory:用于创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
- RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务,默认AbortPolicy.
- AbortPolicy: 直接抛出异常
- CallerRunsPolicy:只用调用者所在的线程来运行任务。
- DiscardOldestPoliy:丢弃队列中最近的一个任务,并执行当前任务。
- DiscardPolicy: 不处理,丢弃掉
- 当然也可以根据应用场景来实现RejectedExecutionHandler接口自定义策略,如记录日志或者持久化存储不能处理的任务。
- keepAliveTime(线程活动保持时间):表示线程没有任务时最多保持多久后停止。默认情况下,只有线程池中线程数大于corePoolSize时,keepAliveTime才会起作用。
- TimeUnit(线程活动保持时间的单位)
其他参数很好理解,可是keepAliveTime的概念可能有点模糊,我们来看一下源码的具体解释。
/*当工作线程的数量大于核心线程数量,那么这个值是那些多余的空闲线程(除核心线程之外的线程)在终止前的最大存活时间。
如果设置allowCoreThreadTimeOut,那么这个值对核心线程也有效。否则,线程池将永远处于存活状态,如果不使用我们
需要手动shutdown,否则将占有资源。*/
private volatile long keepAliveTime;
/*如果该值为false,那么核心线程即使处于空闲状态也不会被销毁。如果该值为true,
那么核心线程将会使keepAliveTime这个值,只要处于某个核心线程空闲状态的时间超过keepAliveTime,
那么该核心线程将被销毁。*/
private volatile boolean allowCoreThreadTimeOut;
可以看到:在默认情况下,工作线程空闲时间达到keepAliveTime的话就会被回收直至线程数为corePoolSize,开启allowCoreThreadTimeOut的话,所有线程空闲时间达到keepAliveTime的话会被回收直至线程数为0。(这里的核心线程指的是线程数量处于coolPoolSize内的线程)
关于ThreadFactory,我们可以来看一个例子:
/*创建一个自己的线程池工厂*/
static class IThreadFactory implements ThreadFactory{
private static final AtomicInteger THREAD_NUMBER = new AtomicInteger(0);
private final ThreadGroup group;
IThreadFactory() {
SecurityManager sm = new SecurityManager();
group = sm.getThreadGroup();
}
@Override
public Thread newThread(Runnable runnable) {
String threadName = "线程: " + THREAD_NUMBER.incrementAndGet();
Thread thread = new Thread(group, runnable, threadName);
LOGGER.info("创建线程: {}", threadName);
return thread;
}
}
ThreadFactory通过newThread来创建线程,我们可以通过newThread来为我们新创建的线程取一个特定的名称,在alibaba规范手册中就有要求让我们创建一个自己的ThreadFactory来指定Thread的名称。
1.3.2. 参数分析
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;
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;
分析一下ThreadPoolExecutor的参数:
ctl: 总共32位,其高3位是用来维护线程池的状态, 其低29位是用来维护线程池中的线程的数量的。初始状态设置为RUNNING,说明该线程池处于运行状态。
- 1.RUNNING: 该状态的线程池会接受新的任务,也会处理在阻塞队列中等待的任务。(高三位为111)
- 2.SHUTDOWN: 该状态的线程池不会接受新的任务,但是还是会处理已经提交到阻塞队列中等待处理的任务。(000)
- 3.STOP: 该状态的线程池不会再接受新的任务,不会处理阻塞队列中等待的任务,而且还会中断正在运行的任务。(001)
- 4.TIDYING: 所有任务都被终止了,workerCount为0,为该状态时还将调用terminated()方法。 (010)
- 5.TERMINATED: terminated()方法调用完成后变成该状态。 (011)
大小关系为 RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED,这个顺序基本上也遵循线程池从运行到终止这个过程。
若线程池打开shutdown()开关的话,那么线程池的状态会由RUNNING->SHUTDOWN.
若线程池打开shutdownNow()开关的话,那么线程池的状态会由RUNNING->STOP.
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; }
runStateOf:获取线程池的运行状态
workerCountOf: 获取线程池中线程的数量
ctlOf(int): rs表示runState,ws表示workerCount,即根据rs和wc合并成ctl。
下面是线程池其他的一些参数:
//线程池存放task的阻塞队列 可由用户指定不同的队列类型
private final BlockingQueue<Runnable> workQueue;
//线程池的主锁 当我们执行统计completedTaskCount,或者从workerQueue添加删除worker时都需要加上该锁
private final ReentrantLock mainLock = new ReentrantLock();
//线程池用来存放worker线程的集合 底层是一个HashSet实现
private final HashSet<Worker> workers = new HashSet<Worker>();
//等待线程池终止的队列 在线程池终止之前,等待线程池终止的线程都被阻塞,放到该Condition上
private final Condition termination = mainLock.newCondition();
//记录线程池最大的容量大小
private int largestPoolSize;
//线程池完成任务的总数量
private long completedTaskCount;
//执行饱和饱和策略的类
private volatile RejectedExecutionHandler handler;
1.3.3. ThreadPoolExecutor核心API分析
- executor(Runnable task) : 提交任务
public void execute(Runnable command) {
//若提交的任务为空 抛出空指针异常
if (command == null)
throw new NullPointerException();
//获取ctl(rs|wc)
int c = ctl.get();
//调用workerCountOf函数计算线程池中核心线程的数量
//若小于corePoolSize,则调用addWorker新建一个新的线程去执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//addWorker失败的原因有:
//1. 线程池已经shutdown了,shutdown之后不会再接受新的任务
//2. 先判断再执行并不是原子操作,在并发情况下,被其他线程抢到资源导致wc>=corePoolSize
//如果调用addWorker失败,需要再次获取ctl,因为在并发环境下ctl随时发生改变
c = ctl.get();
}
//判断线程是否是RUNNING状态并且往阻塞队列中添加任务成功
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get(); //再次进行检验
//再次检验的放入workerQueue是否被执行:
//1.如果线程池不再是RUNNING状态,应该拒绝添加新的任务,从workQueue中删除任务
//2.如果线程池处于RUNNING状态,或者从workerQueue删除任务失败(删除任务失败说明刚好有一个核心线程执行任务完毕并且消耗了该任务,既然已经被消费了,那么就视为任务可执行)。都说明该任务可以继续执行,不需要进行饱和策略。
//在再次检验的过程中,线程池不再处于RUNNING状态且移除任务成功,则对该任务执行饱和策略。
//否则,该任务被视为可以执行。原因如上
if (! isRunning(recheck) && remove(command))
reject(command);
//如果当前workerCount为0,通过addWorker(null, false)创建一个线程,线程的第一个任务为null
//判断为0的原因是: 如果工作线程不为0,说明有线程正在执行,那么就可以运行workQueue中存储的任务。
//如果为0,就创建一个线程来执行workQueue中的任务。只要有活动的线程,就可以消费workQueue中的任务。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果线程池不是RUNNING或者添加无法入队
//则尝试创建新线程来执行它,若失败,则对任务执行包含策略
else if (!addWorker(command, false))
reject(command);
}
我们可以看到execute源码虽然少,但是其中设计的内容还是挺复杂的,毕竟并发环境下,需要注意的点还是挺多。
我们来理一下execute的执行流程:
- 如果线程池当前线程数量少于corePoolSize,则addWorker(command,ture)创建新的线程,如创建成功返回,如没创建成功,则执行后续步骤;
- addWorker失败的可能原因:
- 1.线程池已经shutdown,拒绝接受新的任务。
- 2.workCount<corePoolSize判断后,由于并发,被其他的线程抢占到资源先创建了线程,导致workCount>=corePoolSize.
- 如果线程池还在RUNNING状态,将task加入workQueue中,如果加入成功,进入double-check,如果加入失败(可能是队列满了),则继续执行后续的步骤;
- double-check的主要目的就是判断刚加入workQueue中的task是否能够执行。
- 1、如果线程池不是RUNNING状态,应该拒绝新任务,从workQueue中删除。
- 2、如果线程池是运行状态,或者从workQueue中删除任务失败(刚好有一个核心线程执行完毕并消耗了这个任务,视为成功执行),确保还有线程执行任务(即使只有一个也行)。
- 如果线程池不是RUNNING状态或者无法入队列,尝试开启新线程,扩容至maximumPoolSize,如果addWorker(command,false)失败,则拒绝该任务。
如果我们假设线程池一直处于RUNNING状态,那么execute的流程就如下图:
- 线程池判断核心线程池是否已经满了,如果不是,则创建一个新的工作线程来执行任务,如果核心线程池已经满了,则进入下一个流程。
- 线程池判断工作队列是否已经满了。如果工作队列没有满,则将新提交的任务存储在这个工作队列里,如果工作队列满了,则进入下个流程。
- 线程池判断线程池的线程是否超过maximumPoolSize,如果没有,则创建一个新的工作线程来执行任务,否则,将该任务交给饱和策略来处理。
看完execute,我们来分析一下addWorker函数的源码:
- addWorker(Runnable firstTask, boolean core):新建一个工作线程
参数:firstTask ——新建完该线程后,该线程执行的第一个任务
参数:core ——true,以corePoolSize为上限;false,以maxPoolSize为上限。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//外层循环,负责判断线程池状态
for (;;) {
//获取ctl(wc|rs)
int c = ctl.get();
//获取线程池的工作状态
int rs = runStateOf(c);
//判断为true表示当前线程池不需要再创建线程了
//RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED 这一行对于下面有用
//这个判断条件来解读一下
/**
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()的含义:
我们若想这个条件成立可以满足不需要在添加新的线程,那么只要其中任一个为false即可:
判断:rs==SHUTDOWN false情况(rs>SHUTDOWN):超过SHUTDOWN,线程池已经终止了,不添加新的worker线程。
firstTask==null false情况(rs==SHUTDOWN&&first!=null),该场景是线程池已经SHUTDOWN了,拒绝接收新的任务。
!workQueue.isEmpty() false情况(rs==SHUTDOWN&&first==null&&workQueue.isEmpty()):添加没有任务的新线程就是为了线程池shutdown后处理workQueue中的任务,既然workQueue中已经没有任务了,那么就不需要创建新的线程了。
**/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//内层循环,负责workerCount+1
for (;;) {
//获取线程池中线程的数量
int wc = workerCountOf(c);
//如果临界值(三个临界:线程池允许的最大容量/线程池的核心线程数量/线程池设置的最大容量)那么就不再创建线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
/*通过cas操作进行workerCount+1 若成功则跳出retry继续下面的操作*/
if (compareAndIncrementWorkerCount(c))
break retry;
//再次获取ctl的状态
c = ctl.get(); // Re-read ctl
//若线程池的状态发生变化 则跳到retry 继续进行内外层循环。
if (runStateOf(c) != rs)
continue retry;
//内层进行cas自旋 直至workerCount自增成功 或者线程池状态发生变化 或者wc超过临界值
//cas失败说明有其他线程改变了workerCount
}
}
//worker+1成功后的操作
//添加worker线程到worker Set集合,并启动新线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
/*
1.将worker这个AQS锁的同步状态设置为-1
2.将firstTask设置给worker的成员变量
3.使用worker自身这个runnable,调用ThreadFactory创建一个线程,并设置worker的成员变量thread
*/
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//加锁操作
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//获取线程池运行状态
int rs = runStateOf(ctl.get());
//如果线程池还在运行,或者线程池为shutdown状态,而且firstTask==null(可能workQueue仍有为执行完成的任务,创建没有初始任务的线程去执行workQueue中的任务)
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//如果线程已经启动 抛出非法线程状态异常
if (t.isAlive())
throw new IllegalThreadStateException();
//加当前worker添加到worker Set中
workers.add(w);
int s = workers.size();
//设置线程池最大数量为s
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
//释放锁
mainLock.unlock();
}
//如果成功添加
if (workerAdded) {
//启动线程
t.start();
workerStarted = true;
}
}
} finally {
//如果线程启动失败,执行addWorkerFailed方法。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
我们来理清一下addWorker的流程:
- 判断线程池是否为可以添加worker线程的状态,可以则进入下一步。不可以的原因如下:
- rs > SHUTDOWN, 可能为stop、tidying或者terminated,那么不能添加worker线程。
- 线程池状态为shutdown,firstTask不为空,不能添加worker线程,因为shutdown状态的线程池不接收新任务
- 线程池状态为shutdown,firstTask为空,workQueue为空,不能添加worker线程,因为firstTask为空是为了添加一个没有任务的线程去执行workQueue中的task,而workQueue为空,说明添加无任务,那么创建新的线程已经没有意义。
- 线程池当前线程数量是否超过上限,没超过则worker+1.进入下一步。
- 在线程池的ReetrantLock的保证下,向workers Set添加新创建的worker实例,添加完成解锁,并启动worker线程,如果以上操作都成功,则返回true,如果添加失败或者启动失败,调用addWorkerFailed逻辑。
我们来说一下addWorker的传参的意义:
addWorker方法有4种传参的方式:
- addWorker(command, true)
- addWorker(command, false)
- addWorker(null, false)
- addWorker(null, true)
execute就使用了前三种:
- 线程数小于corePoolSize,放一个需要处理的task进workers Set中。
- 当队列满了,就尝试将这个新的task放入workers Set中。
- 放入一个新的task进worker Set中,创建这样一个worker就是为了让他去workQueue获取任务执行。
- 这个方法就是用来为线程池预先启动核心线程
prestartAllCoreThreads
。
接下来,我们来看一下内部Worker类的实现。
1.3.4. Worker内部类
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Worker类本身既实现了Runnable,又继承了AQS,所有其即是一个可执行的任务,又可以达到锁的效果。
参数
final Thread thread; //此线程用来运行任务
Runnable firstTask; //第一个运行的任务
volatile long completedTasks; //任务计数器
构造器
Worker(Runnable firstTask) {
setState(-1); //设置AQS的初始状态为1
this.firstTask = firstTask;//设置初始任务
this.thread = getThreadFactory().newThread(this);//通过ThreadFactory来创建新的线程
}
API函数源码分析
//调用runWorker来执行任务
public void run() {
runWorker(this);
}
//判断当前线程是否是独占线程
protected boolean isHeldExclusively() {
return getState() != 0;
}
//尝试获取锁
protected boolean tryAcquire(int unused) {
//调用cas获取资源
if (compareAndSetState(0, 1)) {
//获取成功 设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放资源
protected boolean tryRelease(int unused) {
//设置独占线程为null
setExclusiveOwnerThread(null);
//设置状态为0
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;
//如果状态大于等于0 且线程不为null,并且没有被中断过
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
//对线程进行中断
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
其实看到Worker的API,基本上就是独占锁(不可重入)的实现,这里AQS源码就不进行解析了。
我们来看一下runWorker的源码实现:这里才是正在运行firstTask的地方。
final void runWorker(Worker w) {
//获取当前的线程
Thread wt = Thread.currentThread();
//获取任务
Runnable task = w.firstTask;
//将任务设置为null
//将AQS的state设置为0 表示允许中断
w.unlock(); // allow interrupts
//是否“突然完成”,如果是由于异常导致的进入finally,那么completedAbruptly==true就是突然完成的。
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
//上锁,不是为了防止并发执行任务,而是为了在shutdown()时不终止正在运行的worker
w.lock();
//如果线程池处于stop或者以上状态,才会设置中断标志,否则清除中断状态
//如果当前线程池状态>=stop,并且当前线程池没有设置中断标志,那么中断当前线程
//若当前线程被中断且再一次判断线程池的状态>=stop, 由于调用Thread.interrupted()清除了中断标志,那么调用wt.interrupt()中断线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt(); //当前线程调用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);
}
}
runWorker的流程如下:
- Worker线程启动后,会调用Worker自身这个Runnable,执行run()进行runWorker()方法中。
- 执行任务之前,先通过unlock()将AQS的state设置0,表示当前线程允许被中断。
- 开始执行firstTask,调用task.run()方法(这里才算正在地执行了Task),在执行任务之前会进行lock()操作,为了防止任务在运行中被线程池一些中断操作中断。
- 在执行任务前后,可以根据业务场景自定义beforeExecute() 和beforeExecute()方法。
- 若beforeExecute(),beforeExecute(), task.run()在运行过程中发生异常,都会导致当前线程终止,进入processWorkerExit()处理worker退出的流程。
- 若正常执行完task任务之后,会通过getTask()从阻塞队列中获取新的任务。若执行getTask获取任务为空,则退出循环,进入processWorkerExit()处理当前worker。
getTask
private Runnable getTask() {
//用于判断获取任务超时 这里超时是超过keepAliveTime
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
//获取ctl(wc|rs)
int c = ctl.get();
//获取当前线程池的运行状态
int rs = runStateOf(c);
//以下两种情况会导致workerCount-1操作:
//1. 线程池的状态为shutdown且workQueue为null
//2. 线程池的状态>=STOP 此时已经不需要工作线程去执行任务了,即使workQueue不为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取工作线程的数量
int wc = workerCountOf(c);
//默认allowCoreThreadTimeOut为fasle,如果为true的话,说明核心线程和其余工作线程都需要进行
//空闲时间超过keepAliveTime则回收的操作
boolean timed = allowCoreThreadTi为meOut || wc > corePoolSize;
//若当前线程池数量不超过maximumPoolSize且没有超时,继续下面的操作 否则进行workerCount-1操作
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//从阻塞队列中获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//获取到任务就进行返回
if (r != null)
return r;
//在keepAlive时间内没有获取到线程,说明获取任务操作,那么当前线程将被回收(这里没有细说是核心线程还是其余工作线程主要是这要分情况来看,在这里就不进行区分了)
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask的执行流程:
- 首先判断当前线程是否能满足从workQueue获取任务的条件:
- 如果线程池的状态为shutdown且workQueue为null,那么进行decrementWorkerCount
操作并返回null。 - 如果线程池的状态处于stop或以上的状态,不再接受新的任务,也不会执行workQueue中的任务,那么进行decrementWorkerCount操作并返回null。
- 若线程的数量超过maximumPoolSize的话,当前worker线程不会在继续接受新的任务。线程数量会超过maximumPoolSize?应该是调用了setMaximumPoolSize()调整了maximumPoolSize的大小而导致的。
- 若当前线程获取任务超时,即空闲时间超过keepAliveTime,那么当前线程不再接受新的任务。(这里视allowCoreThreadTi为meOut的情况而定)
- 如果线程池的状态为shutdown且workQueue为null,那么进行decrementWorkerCount
- 若当前线程能够获取任务,根据是否需要定时(timed)获取调用不同方法:
- poll()操作:如果在keepAliveTime内没有从阻塞队列获取任务,则返回null,也意味着当前worker线程空闲时间超过keepAliveTime。
- take()操作:如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,获取任务并返回。
- 在获取任务的时候,poll()和take()操作是会响应中断的,若被中断,重置timedOut,继续从流程1开始。
其中decrementWorkerCount是通过cas自旋来实现的。
讲完getTask,我们来了解一下processWorkerExit方法。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果completedAbruptly为true 说明当前worker线程在运行时发生异常
//如果为false不需要wc减少一 因为这个已经在getTask中执行过了
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//把worker的完成任务数加到线程池的完成任务数
completedTaskCount += w.completedTasks;
//从worker Set中移除当前worker线程
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试终止线程池
/*
对线程池有负效益的操作,都需要尝试终止线程池
主要判断线程池是否满足终止的状态
若满足,且线程池中还有线程在运行,尝试对其发出中断响应,使其能进入退出流程
没有线程了,更新状态为tidying->terminated
*/
tryTerminate();
/*
以下代码主要是判断是否需要需要执行addWorker
当前线程池状态为RUNNING或者SHUTDOWN
若当前线程是突然终止的,直接addWorker()
若当前线程不是突然终止的,但是当先的线程数量<需要维护的线程数量,addWorker().
如果allowCoreThreadTimeOut为false,那么至少维护线程池有一个worker线程
如果allowCoreThreadTimeOut为true,那么至少需要维护线程池有corePoolSize个worker线程
*/
int c = ctl.get();
//如果线程池状态是RUNNING或者SHUTDOWN状态,即tryTerminate()没有终止线程池,说明线程池可能还需要线程去执行任务。
if (runStateLessThan(c, STOP)) {
//如果不是突然完成的
if (!completedAbruptly) {
//如果allowCoreThreadTimeOut为true,线程池最少的线程数量的0,即所有worker线程空闲时间超过keepAliveTime,全部被回收
//如果allowCoreThreadTimeOut为false,线程池最少的线程数量是corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果最小线程数量为0,且阻塞队列不为空,那么至少要保留一个线程去处理阻塞队列的任务
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//获取工作线程的数量 如果超过min,不需要添加新的工作线程,达到需要维护的值即可。
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//添加一个没有firstTask的worker
addWorker(null, false);
}
}
processWorkerExit流程:
- worker线程数量减少一:如果completedAbruptly为true,说明是因为异常而退出的,进行decrementWorkerCount操作。若为false,已经在getTask进行wc-1操作,不需要再执行。
- 从Worker Set中删除当前worker,移除过程需要加锁。
- 调用tryTerminate()尝试终止线程。
- 是否需要执行addWorker操作增加工作线程。如果当前线程池状态为RUNNING或者SHUTDOWN,说明线程池可能还需要保持一定的线程数量。
- 若当前线程是突然终止的,直接addWorker()。
- 若当前线程不是突然终止的,但是当先的线程数量 < 需要维护的线程数量,addWorker()。
若allowCoreThreadTimeOut为false,在调用shutdown()方法,直至阻塞队列中任务的数量为空之前,线程池都会维护corePoolSize个线程,然后再逐渐进行线程的销毁。
若allowCoreThreadTimeOut为true,在调用shutdown()方法,直至阻塞队列中任务的数量为空之前,线程池至少要维护1个线程来执行阻塞队列中的任务,然后再逐渐进行线程的销毁。
tryTerminate():尝试终止线程池,该方法在上面processWorkerExit被调用,我们来分析一下:
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//三种情况不需要进行后面的操作
/*
1. 线程池处于RUNNING状态
2. 线程池状态以及处于tidying或者以上状态
3. 线程池状态为SHUTDOWN并且阻塞队列不为空
*/
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//那么进入这一步的有,线程池处于STOP状态,线程池处于SHUTDOWN状态且阻塞队列为空
//若线程池中的工作线程不为0 尝试中断工作线程
if (workerCountOf(c) != 0) { // Eligible to terminate
//至多中断一个空闲的线程
interruptIdleWorkers(ONLY_ONE);
return;
}
//设置线程池的状态 tiding->TERMINATED
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//设置当前状态为TIDYING/ rs=tidying wc=0
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//调用terminated()进行终止
terminated();
} finally {
//最终设置线程池状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
tryTerminate()的执行流程:
- 判断当前线程池是否需要进行终止,以下三种情况不需要:
- 当前线程池处于RUNNING状态
- 当前线程池已经处于TIDYING或者TERMINATED状态,已经正在终止了。
- 当前线程池处于SHUTDOWN状态,但是阻塞队列不为空。
- 若当前线程处于SHUTDOWN且阻塞队列不为空,或者STOP状态,则可以尝试进行终止。则进行下面的步骤,判断线程池里是否还有工作线程,若存在,调用interruptIdleWorkers(ONLY_ONE) 函数去中断空闲的线程。
- 若线程池中已经没有工作线程,那么对线程池进行终止,并唤醒termination队列上的所有线程(这些线程是等待线程池终止的线程)。
interruptIdleWorkers((boolean onlyOne): 若onlyOne为true,那么至多中断一个空闲线程,否则中断所有的空闲线程。
1.3.5. shutdown和shutdownNow
我们来重温一下shutdown和shutdownNow的作用:
shutdown:开始一个有序的关闭,在关闭过程,之前提交的任务会被执行(包含正在执行的,在阻塞队列中的),但新任务会被拒绝。
shutdownNow: 试图停止正在执行的任务,暂停处理正在等待的任务,返回一个等待执行的任务列表。
我们来看一下源码是如何实现的:
shutdown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
//加锁
mainLock.lock();
try {
//判断调用者是否有权限shutdown线程池
checkShutdownAccess();
//设置线程池的状态为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断所有空闲线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}
shutdown执行流程:
- 判断调用者是否有权限执行shutdown()
- 使用CAS操作将线程池状态设置为shutdown,shutdown之后将不再接收新任务。
- 中断所有空闲线程
- 尝试终止线程池
interruptIdleWorkers: 中断所有的空闲worker线程
//onlyOne为true的话中断一个空闲线程 为false的话中断所有的空闲线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//如果t没有被中断过,那么调用tryLock()尝试获取锁
if (!t.isInterrupted() && w.tryLock()) {
try {
//进行中断操作
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
interruptIdleWorkers的流程很简单,遍历worker set,判断当前遍历的worker线程是否被中断过,若没有且获取锁成功,那么就对当前线程进行中断。
w.tryLock()
这个点很重要,我们来重点分析一下:
Worker内部之前分析过,继承了AQS,本身实现了独占锁且不可重入。
我们来看一下worker加锁的代码在哪里?
这段代码在runWorker中:
主要在我们执行任务的时候,会调用lock进行加锁操作,当任务执行完之后,会释放锁,并调用getTask去获取任务。假设我们在worker线程getTask()这段空闲期进行中断操作的话,
那么worker本身实现AQS且不可重入的意义其实就很明显了。
一、相当于一个信号,说明当前线程处于运行状态。
二、结合shutdown的语义可得,我们不能够中断正在运行的worker线程。
这便是Worker内部类继承AQS,实现不可重入的独占锁的价值所在:控制中断。
在调用interruptIdleWorkers()中断线程之前,我们需要调用tryLock获取worker的锁,若获取失败,则说明当前的worker正在运行,不处于空闲状态。所以这也是interruptIdleWorkers()这名字的由来,只会中断空闲的线程。
这个也是shutdown为啥能够让正在执行的任务完成执行的原因。
那既然interruptIdleWorkers()不能中断所有线程,那剩下来正在执行的线程执行任务完毕后,会经过tryTerminate()尝试进行终止。tryTerminate() 的源码我们已经分析过了。这里不再分析了。
然后,被中断的线程会进入finally块执行processWorkerExit方法,从worker Set中移除,并调用tryTerminate()去尝试终止线程池,这样子循环调用,直至线程池的阻塞队列为空,工作线程变成0。
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//判断调用者是否有权限shutdown线程池
checkShutdownAccess();
//CAS自旋设置线程池状态为stop
advanceRunState(STOP);
//中断所有线程,包括正在运行任务的
interruptWorkers();
//将workQueue中的元素放入一个List并返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
return tasks;
}
shutdownNow() 和 shutdown()的大体流程相似,差别主要有如下:
1、将线程池更新为stop状态
2、调用 interruptWorkers() 中断所有线程,包括正在运行的线程
3、将workQueue中待处理的任务移到一个List中,并在方法最后返回,说明shutdownNow()后不会再处理workQueue中的任务
interruptWorkers() 会调用interruptIfStarted()来终止线程,是worker类内部的方法,它不需要尝试获取锁就可以终止线程。
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
需要注意的是,对于运行中的线程调用Thread.interrupt()并不能保证线程被终止,task.run()内部可能捕获了InterruptException,没有上抛,导致线程一直无法结束。
有个小细节要注意一下: runWorker方法中,w.unlock()
,源码解释是允许中断。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
//将AQS的state设置为0 表示允许中断
w.unlock(); // allow interrupts
}
为了让线程真正开始后才可以中断,初始化lock状态为-1,这样子无论是shutdown()调用tryLock或者shutdownNow调用interruptIfStarted()都无法中断相对于的worker线程,只有等到线程正式启动,且调用w.unlock() 时,我们才可以对其进行中断。
这也是为啥我们新建worker实例对象的时候初始化AQS的state为-1的原因,就是为了让线程真正运行时才可以响应中断。
awaitTermination: 等待线程池终止
参数: timeout 超时时间
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
//判断线程池状态是不是TERMINATED
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
//若超时了 线程池还没有终止 直接返回false
if (nanos <= 0)
return false;
//将调用该方法的线程挂起,放入condition中
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
在发出一个shutdown请求之前,在以下三种情况发生之前,调用该方法的线程都会被阻塞。
- 达到超时时间
- 调用该方法的线程被打断
- 所有任务都执行完成,状态变成TERMINATED,并且调用termination.signalAll()唤醒Condition中所有在等待的线程。
如代码所示,调用awaitTermination会判断线程池状态是否为终止状态,若不是且没有到达超时时间,那么会把当前线程放入 termination这个Condition中阻塞一段时间。
termination.awaitNanos() 是通过 LockSupport.parkNanos(this, nanosTimeout) 实现的阻塞等待。
以上便是,关于ThreadPoolExecutor的源码分析部分。