JUC 线程池
一、基本概念
提到线程池,我们首先会想到创建多线程的4中方式:
- 直接继承Thread,重写run方法
- 实现Runnable接口,传入Thread构造中
- new FutureTask( Runnable实现/Callable实现) -> new Thread(futureTask).start()
- threadpool = new ThreadPoolExecutor(.....) -> threadpool( Runnable实现 (其中包括FutureTask) )
关于FutureTask的深入理解,请阅读本博客上一篇FutureTask(未来任务)。
以上4中创建多线程的方式 可以说在扩展性,功能性或者说性能方面 后者会优于前者。那么第四种方式 也就是以线程池的方式来创建多线程有什么好处呢?
- 当执行大量异步任务时线程池能够提供很好的性能(避免重复的创建和销毁线程)
- 线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等
二、线索追踪
JUC包下为我们提供了一套操作多线程的框架Executors, 其中Executors也封装了一些创建线程池的方法:
- newFixedThreadPool:返回固定长度的线程池,线程池中的线程数量是固定的。
- newCachedThreadPool: 返回一个根据实际情况来进行调整的线程数量的线程池,空余线程存活时间是60s.
- newSingleThreadExecutor: 返回只有一个线程的线程池
在这些方法的内部实际上都是使用了 ThreadPoolExecutor
来实现的,只不过对于各个不同的线程池而言,他们的参数不同。
Executors工具实际开发过程中去创建线程池的弊端:
FixedThreadPool
和SingleThreadPool
: 允许的请求队列长度为Integer.MAX_VALUE, 可能会堆积大量请求,从而导致OOMCachedThreadPool
和ScheduledThreadPool
: 允许创建的线程数量为Integer.MAX_VALUE, 可能会创建大量的线程,从而导致OOM
由于Executors在实际开发中创建线程池存在着风险弊端,因此我们要使用自定义的线程池。
如果要自定义线程池,则就需要对ThreadPoolExecutor
这个类的源码展开探究。
三、原理探究
1.成员变量及内部类
/**
高3位:表示当前线程池运行状态
除去高3位之后的低位(29位):表示当前线程池中所拥有的线程数量
**/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//表示在ctl中,低COUNT_BITS位 是用于存放当前线程数量的位(29位)
private static final int COUNT_BITS = Integer.SIZE - 3;
//表示低COUNT_BITS位 所能表达的最大数值29个1 0001 1111 1111 1111 ... 1111 =>5亿多
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
/**
-1的源码 1000 0000 0000 ... 0001
反码 1111 1111 1111 ... 1110
补码 1111 1111 1111 ... 1111
补码 1111 1111 1111 ... 1111 << 29 = 1110 0000 0000 ... 0000
**/
//RUNNING = 1110 0000 0000 ... 0000
private static final int RUNNING = -1 << COUNT_BITS;
//SHUTDOWN = 0000 0000 0000 ... 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//STOP = 0010 0000 0000 ... 0000
private static final int STOP = 1 << COUNT_BITS;
//TIDYING = 0100 0000 0000 ... 0000
private static final int TIDYING = 2 << COUNT_BITS;
//TERMINATED = 0110 0000 0000 ... 0000
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
//获取当前线程池运行状态 也就是获取ctl的高三位
// c: 表示当前ctl的值 ctl存储的是当前线程池的信息
//~CAPACITY = 1110 0000 0000 0000 ... 0000
// eg: c = 1110 0000 0000 0000 ... 0111
// &运算 = 1110 0000 0000 0000 ... 0000 == RUNNNING状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取当前线程池拥有的线程数量 也就是获取clt后29位
// CAPACITY = 0001 1111 1111 1111 ... 1111
// eg: c = 1110 0000 0000 0000 ... 0111
// &运算 = 0000 0000 0000 0000 ... 0111 == 7个线程
private static int workerCountOf(int c) { return c & CAPACITY; }
//计算当前线程池的信息 在重置当前线程池ctl值时会用到
//rs: 当前线程池的5种 二进制值
//wc: 表示当前线程池中 worker(线程) 数量
//eg: RUNNING | 7
// 1110 0000 0000 ... 0000 | 0000 0000 0000 ... 0111 = 1110 0000 0000 ... 0111
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.
*/
//比较当前线程池的ctl所表示的状态,是否小于s
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
//比较 ctl 是否大于等于 s
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
//任务队列,当线程池中的线程达到核心线程数量时,再提交任务,就会直接提交到workQueue
private final BlockingQueue<Runnable> workQueue;
//线程池全局锁,增加worker,减少worker/修改线程池状态 时需要持有mainLock
private final ReentrantLock mainLock = new ReentrantLock();
//线程池中真正存放worker->thread 的地方
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
Object wait notifyAll
Condition await signalAll
LockSupport park unpark
**/
//当外部线程调用 awaitTermination() 方法时, 外部线程会等待当前线程池状态为Termination为止。
//等待是如何实现的? 就是将外部线程 封装成WaitNode(thread外部线程) 放入到Condition队列中了,被LockSupport.park(处于waiting状态)
//当线程池状态 变为Termination时 会通过termination.signalAll() 唤醒Condition队列中的线程,
//唤醒之后这些线程会进入到 阻塞队列, 然后头节点回去抢占mainLock.抢占到的线程,会继续执行awaitTermination()后面的程序。
private final Condition termination = mainLock.newCondition();
//记录线程池声明周期内,线程数最大值
private int largestPoolSize;
//记录线程池所完成任务总数,当worker退出时 会将worker完成的任务数量累计到该处
private long completedTaskCount;
/**
当我们使用Executors.newFixed / cached 创建线程池时,使用的是DefaultThreadFactory(一般不建议使用Default线程池,推荐自己实现)
**/
//创建线程时会使用 线程工厂
private volatile ThreadFactory threadFactory;
//拒绝策略,juc包下提供了4中方式 默认采用Abort..抛出异常的策略
private volatile RejectedExecutionHandler handler;
//空闲线程存活时间 当allowCoreThreadTimeOut == false时,会维护核心线程数量内的线程存活,超出部分会被超时
//当allowCoreThreadTimeOut == true时,核心数量内的线程 空闲超时时 也会被回收。
private volatile long keepAliveTime;
//控制核心线程 超时 是否可以被回收。 true 可以 false 不可以
private volatile boolean allowCoreThreadTimeOut;
//控制核心线程数量限制
private volatile int corePoolSize;
//控制线程池最大线程数量限制
private volatile int maximumPoolSize;
//默认的拒绝策略AbortPolicy,采用的是抛出异常的方式
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
核心成员Worker
/**
Worker采用了AQS的独占模式
独占模式:两个重要的属性 state 和 ExclusiveOwnerThread
state: 0时 表示未被占用 >0时 表示被占用 <0时 表示初始状态这种情况下不能被抢锁
ExclusiveOwnerThread:表示独占锁的线程
**/
private final class Worker extends AbstractQueuedSynchronizerimplements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
//worker内部封装的线程
final Thread thread;
//假设firstTask不为空,那么当worker启动后(内部的线程启动)会优先执行firstTask,当firstTask执行完后,会去Queue中获取下一个任务
Runnable firstTask;
//记录当前worker所完成任务数量
volatile long completedTasks;
//fisrtTask 可以为null。 为null 启动后会到Queue中获取任务
Worker(Runnable firstTask) {
//设置AQS独占模式为初始化中状态,这个时候不能被抢占锁
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//使用线程工厂创建了一个线程,并且将当前worker指定为Runnable.也就是说当thread线程启动时,会以worker.run()为入口
this.thread = getThreadFactory().newThread(this);
}
//当worker启动时会 执行run()
public void run() {
//ThreadPoolExecutor->runWorker() 这个是核心方法,等候面分析worker启动后逻辑时 以这里为切入。
runWorker(this);
}
//判断当前worker的独占锁是否被独占
//0 表示未被占用
//1 表示已占用
protected boolean isHeldExclusively() {
return getState() != 0;
}
//尝试去占用worker的独占锁
protected boolean tryAcquire(int unused) {
//使用CAS修改AQS中的 state,期望值为0(未占用),
//修改成功表示当前线程抢占成功,那么设置ExclusiveOwnerThread为当前线程。
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//外部不会直接调用这个方法,这个方法是AQS内调用的,外部调用unlock时,unlock->AQS.release()->tryRelease()
//尝试去释放锁
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//加锁,加锁失败时,会阻塞当前线程,直到获取到锁。
public void lock() { acquire(1); }
//尝试去加锁,如果当前锁是未被持有状态,那么加锁成功后,会返回true,否则不会阻塞,直接返回false
public boolean tryLock() { return tryAcquire(1); }
//一般情况下 调用unlock要保证 当前线程是持有锁的。
//特殊情况 当worker的state == -1时,调用Unlock 表示初始化state 设置state==0
//启动worker之前会先调用unlock()这个方法。 会强制刷新ExclusiveOwnerThread = null 和 state=0
public void unlock() { release(1); }
//就是返回当前worker的lock是否被占用。
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
2.构造方法
因为有很多构造方法,且内部都是套娃,最终都会调用该最长的构造方法,那么我们就直接来看此构造。
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
:最大线程数量 且 maximumPoolSize
必须大于 corePoolSize
keepAliveTime
:空闲线程存活时间
unit
:空闲线程存活时间单位
workQueue
:任务队列,存放任务的集合
threadFactory
: 创建线程的工厂 绑定在worker内,也就是new一个worker时,会调用该方法创建一个线程
handler
:拒绝策略 后面会详细说有哪些拒绝策略
3. 核心方法
执行入口 execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取最新的ctl
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);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
if (command == null)
首先会判断提交的任务是否为空,防止抛空指针异常。
除了第一个防止空指针的判断外,还有3个判断代码块。 下面逐一击破。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (workerCountOf(c) < corePoolSize)
: 判断当前线程池中的线程数量是否小于核心线程数量
条件成立则进入代码块内
if (addWorker(command, true))
:判断 添加线程操作是否成功,若成功则直接结束,若不成功则重新获取最新的ctl
。
addWorker(command, true)
不成功的原因是什么呢?
因为这里的参数是 firstTask = command !=null
,core = true
那么不成功的原因有两种:
- 当前线程池的状态为非
RUNNING
状态 (RUNNING
,SHUTDOWN
,STOP
,TIDYING
,TERMINATED
) - 当前线程池的状态为
RUNNING
,但是当前线程池中的线程数量 大于等于corePoolSize
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);
}
if (isRunning(c) && workQueue.offer(command))
:
该条件不成立:当前线程池的状态为非RUNNING
,
该条件成立:当前线程池状态为RUNNING
且向任务队列(workQueue
)中成功添加任务
条件成立执行代码块内容
int recheck = ctl.get();
:再次获取最新的ctl
if (! isRunning(recheck) && remove(command))
:
条件成立: 当前线程池状态为非RUNNING
且 成功将任务移除任务队列 。那么会去执行拒绝策略。
条件不成立有两种情况:1.当前线程池状态为RUNNING
2.当前线程池状态为非RUNNING
,且从任务队列中移除任务失败。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
当线程池中的工作线程数量为0时,则需要添加一个线程
else if (!addWorker(command, false))
reject(command)
执行到这有两种情况:
- 当前线程池的状态为
RUNNING
,但是当前线程池中的线程数量 大于等于corePoolSize
,且任务队列已满。 - 当前线程池的状态为非
RUNNING
状态
else if (!addWorker(command, false))
:
条件成立: 添加线程失败 执行拒绝策略
条件失败:添加线程成功 方法结束
小结:
rs
:当前线程池运行状态 wc
: 当前线程池中线程数量 command
: 任务
成功添加任务:
rs
==RUNNING
&&wc
<corePoolSize
rs
==RUNNING
&&wc
>=corePoolSize
&&workQueue
. offer(command
)rs
==RUNNING
&&wc
<maximumPoolSize
1,3 的任务都是添加到了 worker
的firstTask
中
2 的任务是添加到了任务队列(workQueue
)中,等待着线程池中空闲线程来消费
被拒绝策略:
rs
==RUNNING
&&wc
>=corePoolSize
&&workQueue. offer(command)
&&rs != RUNNING
&&workQueue.remove(command)
->reject(command)
rs != RUNNING
&&command != null
&&wc >=maximumPoolSize
->reject(command)
rs != RUNNING
&&command != null
->reject(command)
这里的成功或失败并不是非常细节,具体还需要参考添加addWorker()
方法中的逻辑。
添加线程addWorker
该方法的主要目的就是为了向线程池中创建线程(worker),并执行线程。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
该方法代码很长,也很难理解。 这里我将它分成两个部分。逐一击破。
private boolean addWorker(Runnable firstTask, boolean core) {
//第一部分: 主要是判断当前线程池是否可以添加线程
retry:
for (;;) {...}
//第二部分:执行添加线程和执行线程的逻辑
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {...} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
这两部分主要的工作内容由注释可知。
第一部分
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//....第二部分的代码省略
}
这是第一部分的代码。 在前面execute()
方法中 频繁的调用了 addWorker()
方法,但是参数都不相同。
//添加一个有初始任务的线程,且当前线程池中的数量加上该线程小于等于核心线程数
addWorker(command, true)
//添加一个有初始任务的线程,且当前线程池中的数量加上该线程小于等于最大线程数
addWorker(command, false)
//添加一个没有初始任务的线程,且当前线程池中的数量加上该线程小于等于最大线程数
addWorker(null, false)
addWorker()
方法中两个参数 Runnable firstTask
, boolean core
的含义分别是:
-
Runnable firstTask
: 代表提交的任务。 当
firstTask
为null时 , 创建的是一个不带初始任务的线程(worker) -
boolean core
:代表是否使用核心线程数的限制 true 代表使用核心线程数(
corePoolSize
)的限制 false 代表使用最大线程数(
maximumPoolSize
)的限制
retry:
for (;;) {
//获取最新的ctl,并根据ctl获取当前线程池的状态
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {...}
}
这里有一个外自旋中内嵌了一个内自旋。 先看内自旋外的代码。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
条件成立则不允许往线程池中添加线程。
从该条件判断中得知有以下几种情况不允许往线程池中添加线程:
- 当前线程池的状态 >
SHUTDOWN
(RUNNING
,SHUTDOWN
,STOP
,TIDYING
,TERMINATED
) - 当前线程池的状态 =
SHUTDOWN
且 创建的线程(worker
)会有初始任务(firstTask!=null
) - 当前线程池的状态 =
SHUTDOWN
, 且 没有初始任务(firstTask==null
) 但任务队列是空(workQueue.isEmpty
())
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
换言之若要走到内自旋,首先要保证有以下两种情况的其中之一:
- 当前线程池的状态是
RUNNING
- 当前线程池的状态是
SHUTDOWN
,不带有初始任务(firstTask
) 且任务队列(workQueue
)不为空
int wc = workerCountOf(c);
:根据ctl
获得目前线程池中的线程数量
if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))
: 前面说过core
参数是为了规定添加线程要使用哪种数量限制。根据当前线程池的线程数量与规定的限制比较来决定能否继续添加线程。
if (compareAndIncrementWorkerCount(c)
: 该条件判断中是CAS操作,走到该条件时,说明当前线程池已经允许添加线程了。 若该CAS操作成功 则退出外自旋,去执行添加线程和执行线程的操作。 若该CAS失败,则根据后面的判断决定是继续内自旋,还是重新外自旋。
compareAndIncrementWorkerCount(c)
:该CAS操作就是将ctl
值 加1 (当前线程池中的线程数量加1) , 其主要目的就是为了与其它提交任务的线程争抢该线程池的位置,以此来确保自己不被corePoolSize
或者maximumPoolSize
限制,可以理解拿到了一块可以执行 创建线程,添加线程池,执行线程的令牌。
c = ctl.get(); if (runStateOf(c) != rs)
: 目的是为了确保当前线程池的状态是否被修改。若被修改,则需要进入外自旋重新根据线程池状态来决定是否可以向线程池添加线程。若没有被修改,则继续内自旋去与其它提交任务的线程来抢令牌。
小结:
第一部分的代码主要是判断当前线程池是否可以添加线程。 那么允许添加线程的情况有以下几种:
- 当前线程池的状态是RUNNING, 且当前线程池中的线程数量 小于
corePoolSize
或maximumPoolSize
。 - 当前线程池的状态是SHUTDOWN, 没有初始任务(
firstTask
), 任务队列(workQueue
)中有任务, 且当前线程池中的线程数量 小于corePoolSize
或maximumPoolSize
。
第二部分
private boolean addWorker(Runnable firstTask, boolean core) {
//第一部分代码省略...
retry:
for (;;) {...}
//第二部分
//线程worker是否启动
boolean workerStarted = false;
//线程worker是否添加
boolean workerAdded = false;
//线程worker的引用 w
Worker w = null;
try {
//创建worker
w = new Worker(firstTask);
// 将worker的thread 赋值给t
final Thread t = w.thread;
//判断worker内的thread是否为空,这里主要是为了防止自定义的ThreadFactory有BUG
if (t != null) {
//赋值全局锁
final ReentrantLock mainLock = this.mainLock;
/**
获取全局锁
主要目的: 1.保证锁内的操作是原子操作,不会受其它外界影响
2.在线程池内部 对线程状态状态修改都需要获取这把全局锁
**/
mainLock.lock();
try {
//再次校验,为了防止在上锁前,有外部线程修改了线程池的状态。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//这里也是为了防止自定义的ThreadFactory有BUG
if (t.isAlive())
throw new IllegalThreadStateException();
//将线程worker添加进线程池 (HashSet workers)
//由于前面获取到了令牌,因此这里一定会添加成功
workers.add(w);
//更新线程池声明周期内的最大线程数量值
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//设置worker添加成功
workerAdded = true;
}
} finally {
//释放全局锁
mainLock.unlock();
}
/**
条件成立: 启动线程,设置启动线程成功
条件不成立: 说明在上全局锁前,有外部线程修改了线程池的状态
**/
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//条件成立: 说明启动失败
if (! workerStarted)
/**
需要做清理工作:
1.释放令牌 ctl-1
2.workers.remove(w)
**/
addWorkerFailed(w);
}
//返回最终的添加结果
return workerStarted;
}
第二部分是addWorker()
的核心部分,虽然是核心但理解起来并不难。其主要工作就是以下三步:
- 创建线程
w = new Worker(firstTask);
- 添加线程池
workers.add(w);
- 执行线程
t.start();
执行任务runWorker
在addWorker()
方法中,会执行t.start()
启动线程 ,Thread 内部调用 native start0()
方法,让操作系统来创建一个线程,并执行Thread中的run()
方法,在内部又调用了 target.run()
而这个target
就是worker
对象的引用。那么就会直接来到Worker
中的run()
方法
public void run() {
runWorker(this);
}
接着也就走到了runWorker
()这个方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
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) {...}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
Thread wt = Thread.currentThread();
:将当前执行的worker的线程赋值给wt ,
这里为什么不使用Thread wt = w.thread
赋值呢?本人也不清楚,如果有知道的小伙伴欢迎评论里留言。
Runnable task = w.firstTask;
:将worker的初始任务取出赋值给task
w.firstTask = null;
: 并将worker中的初始任务引用赋值为空。
w.unlock();
:没了解过AQS
的同学肯定会觉得很诡异。一般lock
与 unlock
不应该是成对出现么,为什么这里会先unlock
呢,感兴趣的同学可以期待我后面的博客文章,会专门解读AQS
独占锁的源码。
这里提前unlock()
的主要原因如下:
因为在新创建的Worker 其AQS
中的state属性为-1
//Worker的构造方法
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//父类AQS 会调用tryRelease()走到该子类的方法中
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);//将持有独占锁的线程清空
setState(0); //将独占锁的状态置为0
return true;
}
public void unlock() { release(1); }
暂时可以理解为Worker的AQS
尚未初始化, 而提前unlock()
就是为了先初始化。
目的是将 setState(0)
setExclusiveOwnerThread(null)
。
boolean completedAbruptly = true;
: 这个字段主要表示是否突然退出。
true: 代表当前线程由于抛出异常突然退出了
false: 代表当前线程正常退出
final void runWorker(Worker w) {
//前面的赋值操作省略...
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
while (task != null || (task = getTask()) != null)
: 这个while循环的主要目的就是为了让线程不断的获取任务并去执行。
条件成立:说明当前线程有任务可以去消费,可能是初始任务,也可能是从任务队列中获取的任务。
条件不成立:说明当前线程没有任务消费了(没有初始任务或者初始任务在上一次循环消费完了,而且任务队列中也没有任务了),会正常退出循环,然后执行线程退出逻辑。
w.lock();
:设置独占锁。之前介绍过Worker
继承了AQS
,因此worker
自身是把独占锁。
这时你可能会有疑问,每个worker都会有自己的独占锁,且这些独占锁各不相同,这里也不存在着竞争关系,那么为什么要加锁呢?
主要目的是为了 当有人执行shutdown方法时,会去线程池挨个判断这些worker的状态,根据worker中的独占锁是否空闲来判断当前worker是否在工作中。 若在工作中的线程不会被打断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
这段条件判断主要理解为:
1.若当前线程池的状态 >= STOP 则需要将当前worker
线程设置为中断状态
- 若当前线程池的状态 < STOP 则要保证工作线程都没有中断标志。
beforeExecute(wt, task);
:钩子方法,空方法,留给子类继承实现扩展的
Throwable thrown = null;
:抛出异常的引用
task.run();
: 运行任务 task可能是FutureTask
, 也可能是普通的Runnable
接口实现类
afterExecute(task, thrown);
:钩子方法,空方法,留给子类继承实现扩展的
task = null;
:将执行完的task置为空,再次循环获取新task
w.completedTasks++;
:更新worker完成任务数量
w.unlock();
:释放独占锁
completedAbruptly = false;
:当while循环时获取不到task了 则会走到该处 设置为false 表示正常退出
processWorkerExit(w, completedAbruptly);
:执行线程退出的逻辑,来到这有可能是异常退出(当task.run抛出异常时,则直接从w.unlock跳到这里),也有可能是正常退出。
获取任务getTask
private Runnable getTask() {
//标记当前获取任务的线程是否超时 true超时 false没超时
boolean timedOut = false; // Did the last poll() time out?
//自旋
for (;;) {
//获取最新ctl 并从ctl中拿到当前线程池的运行状态
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//拿到当前线程池中的线程数量
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
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;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
来到一个条件判断,主要目的 根据当前线程池的情况判断是否具备销毁线程的条件。
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
首先看条件成立的情况下 大括号里会做什么
decrementWorkerCount(); return null;
: 当前线程池中的线程数量-1,并返回null。
那么再看判断条件 rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())
:
什么情况下会条件成立呢,有两种:
1.当前线程池的运行状态 > STOP
2.当前线程池的运行状态 = SHUTDOWN
且 任务队列为空。
换句人话说:如果当前线程池的状态是SHUTDOWN 且任务队列中没有任务,或者 当前线程池状态是STOP了。那么线程就不允许获取任务,且该线程要被回收了。
接着看这一行
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
timed
这个参数表示 线程获取任务时是否支持超时机制。
false 不支持超时机制, 那么就会使用 workQueue.take()
线程就会一直阻塞在这,直到队列中有任务。
true 支持超时机制,那么就会使用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
线程也会阻塞获取任务,但是会有时间限制,当规定之间到了 还没有获取到任务 则返回null。
allowCoreThreadTimeOut
参数代表 是否会回收核心线程数以内的线程。
true代表会回收 false 代表不会回收 该参数的默认值为 false
那么目前我们就只考虑 wc > corePoolSize
当然也很容易理解: 当线程池中工作线程数大于corePoolSize,则使用 poll() 带有超时机制的阻塞方式获取任务
当线程池中工作线程数小于等于corePoolSize,则使用take() 阻塞方式获取任务
那么看下面这段,主要目的就是 判断是否具备销毁线程的条件。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
条件1:wc > maximumPoolSize || (timed && timedOut)
条件2:wc > 1 || workQueue.isEmpty()
当wc > maximumPoolSize
什么情况下会成立? 当重置了maximumPoolSize时,会出现这种情况。
那么条件1成立的情况可以描述为: 要么线程池中的工作线程数大于最大线程数的线程, 要么是 经历过获取任务超时的线程 才有可能被销毁。
条件1满足的情况下才会去判断条件2
那么条件2可以描述为:在条件1成立的基础上,要么当前线程工作数量大于等于2个 或者 任务队列已经是空的情况下 该线程会被销毁。
那么满足以上判断都成立的i情况下,会执行下面的代码
if (compareAndDecrementWorkerCount(c))
return null;
continue;
条件成立则说明当前线程池工作线程数量-1 并返回null,没有获取到任务,该线程要被回收。
条件不成立则说明在之前 有线程先一步执行了-1操作了 或者 当前线程池的状态被修改了。
剩下这段代码 就是根据是否支持超时机制,以不同的方式从任务队列中获取任务。
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//获取到了任务直接返回, 在runWorker中调用 task.run()执行任务
if (r != null)
return r;
//若获取到任务为空,该种情况下只能为使用poll方式获取任务
timedOut = true;
} catch (InterruptedException retry) {
//该情况是take方式获取任务,线程一直阻塞,直到被interrupt中断 才被唤醒
timedOut = false;
}
小结:
getTask
方法什么情况下会返回null 直接奉上这段原注释 言简意赅:
returns null if this worker must exit because of any of:
1. There are more than maximumPoolSize workers (due to a call tosetMaximumPoolSize).
2. The pool is stopped.
3. The pool is shutdown and the queue is empty.
4. This worker timed out waiting for a task, and timed-out workers are subject to termination (that is, {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
both before and after the timed wait, and if the queue is non-empty, this worker is not the last thread in the pool.
1. WorkerCount > maximumPoolSize
2. RunState == STOP
3. RunState == SHUTDOWN && WorkQueue is empty
4. 前提:线程已经超时获取过一次任务了 timeOut = true。 当allowCoreThreadTimeOut=true(核心线程数以内的线程会被回收) 或者workerCount > corePoolSize(当前工作线程数 大于 核心线程数限制了), 且工作队列不为空 当前线程也不是该线程池中最后一个线程。
线程退出回收processWorkerExit
在runWorker()
方法中的最后会执行该退出逻辑
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
来到该方法有两种线程 1.正常退出completedAbruptly = false 2.异常退出completedAbruptly = true
正常退出 是因为getTask
方法中获取的为null ,
异常退出 可能是因为task.run()
执行任务具体逻辑时抛出了异常,也可能是因为在getTask方法中线程阻塞获取任务时被中断了抛出异常
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
当线程是异常退出时,工作线程数需要减1
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//将该线程所完成的任务数量汇总到全局线程池完成的任务数量记录中
completedTaskCount += w.completedTasks;
//从线程池中移除该线程
workers.remove(w);
} finally {
mainLock.unlock();
}
这里因为操作了两个全局变量,所以加上了全局锁。
tryTerminate();
该方法后面会将,这里只需要知道时尝试关闭线程池。
int c = ctl.get();
//当前线程池状态为 RUNNING 或者 SHUTDOWN 才能进入下面的代码
if (runStateLessThan(c, STOP)) {
//条件成立:线程正常退出
//条件不成立: 线程异常退出 会直接执行后面的addWorker(null, false)
if (!completedAbruptly) {
//min代表当前线程池需要维护的最小线程数量
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
:
min
字段代表当前线程池需要维护的最小线程数量
if (min == 0 && ! workQueue.isEmpty())
min = 1;
条件成立: 当前当前线程池需要维护的最小数量为0,且任务队列不为空,那么要设置当前线程池中最少要有1个工作线程存在。
if (workerCountOf(c) >= min)
return; // replacement not needed
条件成立: 当前线程池中的工作线程数量 大于 最小维护量的话 那么说明正常 不需要做任何事。
当来到addWorker(null, false);
时说明 该线程池中的数量没有达到最小维护值,则需要添加线程。
线程池关闭 shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
//由于要操作线程池的全局变量 因此要加全局锁。
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
//空方法,留给子类实现的扩展用的
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
在该方法中又调用了很多方法,那么接下来就来解析其中调用的方法。
首先来看checkShutdownAccess();
该方法是权限相关的,也就是检查是否有权限关闭线程池。这里不做过多解释,不是很重要。
1.advanceRunState()
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
该方法目的就是提前将线程池的状态设置为SHUTDOWN, 其中逻辑并不复杂也就是使用了CAS
操作。
2.interruptIdleWorkers()
该方法目的是中断空闲线程。
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
//加全局锁。 实际上这里就是可重入锁的体现。
mainLock.lock();
try {
//循环遍历线程池中的所有线程
for (Worker w : workers) {
//获取到w.thread
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
//由于本例传参是false 所以会对所有的空闲线程设置中断标记
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
有注释的看注释,没注释下面我会详细解释
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
!t.isInterrupted() && w.tryLock()
:判断该线程是否需要被设置中断
哪些线程需要被设置中断? 空闲且没有被设置中断标记的线程
!t.isInterrupted()
: 这是判断线程是否设置了中断标记的
w.tryLock()
:尝试去获取锁,true代表获取到 false代表没获取到。在runWorker
中加过锁,目的就是在此,工作中的线程不会被打断。
那么为什么要给线程打上中断标志呢?
是因为在执行getTask()方法时,当线程阻塞在获取任务时,在阻塞过程中若设置该线程的中断标志,则会抛异常,那么紧接着就会去执行processWorkerExit
回收线程。
总结:线程池状态大于等于SHUTDOWN 时,会调用该方法,将所有空闲的线程打个中断标志。
被打中断标志的线程,会在 getTask()
方法中抛出异常,从而在后一个方法中销毁线程。
若销毁线程后,阻塞队列中还有任务,还是可以新建 Worker,继续消费队列中的任务
3.tryTerminate()
final void tryTerminate() {
//自旋
for (;;) {
//获取最新的ctl
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
此方法在processWorkerExit
方法中也调用过,正好在这里可以讲解。
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
首先看这段,判断线程池状态的。 三个条件或判断,只要一个条件成立判断就成立。
isRunning(c)
: 线程池为RUNNING状态时,不处理。
runStateAtLeast(c, TIDYING)
: 线程池状态大于等于TIDYING
时不处理。这个说明已经执行过tryTerminate
方法了。
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())
:线程池状态为SHUTDOWN
且任务队列不为空,不处理。因为还有任务,需要让它把活干完才行。
除此之外,就剩两种状态需要处理了:
线程池状态为STOP,可以关闭线程池
线程池状态为SHUTDOWN,且队列为空 ,可以关闭线程池
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE); //中断一个空闲线程
return;
}
当工作线程大于0时, 中断一个空闲的线程。
interruptIdleWorkers
这个方法,前面讲过了,只是参数变成了true,只中断一个空闲线程。
在 shutdown()
这个方法中,所有空闲线程都被中断了。简化的考虑,这段没用。
但是在 processWorkerExit()
方法中,调用 tryTerminate()
是有意义的。
它可以让阻塞的线程,从 getTask()
方法中,跳出来,进入processWorkerExit()
被销毁掉。
同时会触发再次中断一个空闲线程,使其从getTask()
方法中,跳出来,进入processWorkerExit()
被销毁掉。这就形成了一个循环,把空闲的线程一个一个处理掉了。
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//线程池状态设置为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//留给子类实现的,钩子方法
terminated();
} finally {
//最终再将线程池状态修改为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
//唤醒所有阻塞的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
这段的主要意思就是 获取主锁,先将线程池状态修改为TIDYING,然后执行 钩子方法terminated()
,最终再将线程池状态修改为TERMINATED,termination.signalAll()
唤醒所有阻塞的线程。