ThreadPoolExecutor 一些操作的分析
严格来说此内容并不是一个源码解读,只是在最近使用线程池的时候发现了一些之前认识误区的地方。和源码相比更关注一些方法使用的反馈,所以会显得比较简陋
这里主要是介绍下面内容
- 线程池使用哪个参数记录线程数和线程状态
- 线程池状态以及互相装换
- 线程创建的时机
- 线程关闭的时机
- shutdown和shutdownNow的区别
线程池使用哪个参数记录线程数和线程状态
线程数的参数
ThreadPoolExecutor要确定线程池中线程数需要三个参数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
线程数其实是和线程池状态使用同一个参数来确定的ctl
。高3位线程池状态,低29位来确定线程个数
private static final int COUNT_BITS = Integer.SIZE - 3
所以准确说是具体平台下Integer的二进制位数-3后的剩余位数才是线程的个数
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
线程可以设置的最大个数(低29位)00011111111111111111111111111111
线程池状态
参数 | 值 | 二进制 |
---|---|---|
RUNNING | -1 << COUNT_BITS | 11100000000000000000000000000000 |
SHUTDOWN | 0 << COUNT_BITS | 00000000000000000000000000000000 |
STOP | 1 << COUNT_BITS | 00100000000000000000000000000000 |
TIDYING | 2 << COUNT_BITS | 01000000000000000000000000000000 |
TERMINATED | 3 << COUNT_BITS | 01100000000000000000000000000000 |
线程池状态以及互相装换
线程池状态 | 说明 |
---|---|
RUNNING | 线程池正常运行,接受新任务并正常处理队列的任务 |
SHUTDOWN | 线程池即将关闭,拒绝新任务但正常处理队列的任务 |
STOP | 线程池关闭,拒绝新任务并且抛弃阻塞队列里的任务同时中断正在处理的任务 |
TIDYING | 任务执行完毕,并且活跃线程为0,即将TERMINATED状态 |
TERMINATED | 终止状态 |
状态转换
- 当调用了shutdown() 或者 finalize() 方法
RUNNING -> SHUTDOWN
- 当调用了shutdownNow() 方法
RUNNING -> STOP
SHUTDOWN -> STOP
- 当线程池和任务队列都为空的时候
SHUTDOWN -> TIDYING
- 线程池为空的时候
STOP -> TIDYING
- 当 terminated() hook 方法执行完成时候
TIDYING -> TERMINATED
线程创建的时机
线程池中线程创建的时机可以查看任务提交的方法execute
。其创建工作线程的方法为addWorker
。从下面的逻辑可以看出创建的时机
执行任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 此值其实是线程池运行状态和线程数的组合值
int c = ctl.get();
// 当前工作线程小于核心线程数,才会执行创建工作线程的操作且此时的工作线程为核心线程,并且当前任务会作为新线程的第一个任务执行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果线程池运行正常,且任务可以正常添加到队列中会进入二次检测,二级检测主要是根据线程池当前的运行状态进行来判断
// 而创建的线程则是非核心线程
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 此时队列满了,这个时候需要尝试添加非核心线程并且当前任务会作为新线程的任务,如果依旧失败,则拒绝
else if (!addWorker(command, false))
reject(command);
}
这里可以看到创建工作线程分为三个逻辑(其实上源码中注释已经说明了),其大致流程;
- 如果运行的线程少于corePoolSize,尝试以给定的任务作为线程第一个任务,创建新线程
- 如果任务成功进入队列中,此时需要二次检测是否需要添加线程(为了避免在上次检测后有死亡的线程),或者线程池关闭,所以重新检查状态,
- 如果任务无法进行排队,则尝试添加新的线程,如果失败则使用拒绝策略
添加工作线程
添加线程池线程的逻辑很多,这里只是来介绍线程池创建时机所以只列出重点内容
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
......
for (;;) {
int wc = workerCountOf(c);
// 如果创建的是核心线程则此时工作线程不能大于核心线程
// 如果创建的是非核心线程此时创建的工作线程不能大于非核心线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
......
}
}
......
return workerStarted;
}
上面内容可以看到,根据core的不同,会判断线程池是否为核心线程创建逻辑(当然在线程清理的时候并不会判断其是不是核心线程),然后根据不同的参数判断是否达到上限。
图示
后来的任务先执行
从上面内容可以发现,当队列满且依旧可以创建非核心线程的时候,此时添加的任务会导致非核心线程的创建,同时此任务会作为非核心线程的第一个任务,而不是队列中的任务。
这里使用下面的例子可以发现无论执行多少次任务0-2和任务6、7都会第一时间执行。
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(3));
List<Runnable> runnables = new ArrayList<>();
for (int i = 0; i < 8; i++) {
int finalI = i;
runnables.add(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务" + finalI);
});
}
for (Runnable runnable : runnables) {
threadPoolExecutor.execute(runnable);
}
}
线程关闭的时机
这里可以查看工作线程执行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) {
......
}
completedAbruptly = false;
} finally {
// 任务清理
// 工作线程中清理当前任务
// 尝试设置线程池状态为TERMINATED,如果当前是shutdonw状态并且工作队列为空,或者当前是stop状态当前线程池里面没有活动线程
// 如果当前线程个数小于核心个数,则增加
processWorkerExit(w, completedAbruptly);
}
}
在无法从队列中获取任务之后,就开始执行线程的清理工作
清理线程
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();
}
// 尝试设置线程池状态为TERMINATED,如果当前是shutdonw状态并且工作队列为空
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);
}
}
shutdown和shutdownNow
shutdown关闭线程池
shutdown 操作:调用 shutdown 后,线程池就不会在接受新的任务了,但是工作队列里面的任务还是要执行的
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回
advanceRunState(SHUTDOWN);
// 设置中断标志
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试状态变为TERMINATED
// 如果当前线程池状态是 shutdonw 状态并且工作队列为空或者当前是 stop 状态当前线程池里面没有活动线程则设置线程池状态为 TERMINATED,
// 如果设置为了 TERMINATED 状态还需要调用条件变量 termination 的 signalAll()方法激活所有因为调用线程池的 awaitTermination 方法而被阻塞的线程
tryTerminate();
}
shutdownNow关闭线程池
线程池就不会在接受新的任务了,并且丢弃工作队列里面里面的任务,正在执行的任务会被中断,该方法是立刻返回的,并不等待激活的任务执行完成在返回。返回值为这时候队列里面被丢弃的任务列表。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置当前线程池状态为STOP,如果已经是STOP则直接返回
advanceRunState(STOP);
// 中断所有线程
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
shutdown和shutdownNow主要在于状态调整和对线程的操作
- 状态调整
shutdown使用advanceRunState(SHUTDOWN)
将线程池状态设置为SHUTDOWN
shutdownNow使用advanceRunState(STOP)
将线程池状态设置为STOP
- 对线程的操作
shutdown会调用线程的
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();
}
}
shutdown会调用线程的
interruptIfStarted()
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。