概述
最近刚好在学多线程的东西,并且项目中有用到多线程,然后在一次review代码的时候,大佬提出了一个问题,pool.shutdown()
之后,不一定所有线程就立刻停止,最好加上 pool.awaitTermination()
方法给一定的等待时间,给点时间让剩余线程结束。
当时对这个东西不怎么熟悉,为什么 shutdown()
后还是不会立刻把全部线程停止呢?然后去看了下源码的解析才发现,原来jdk是搞了两套逻辑。
线程池状态
在看之前,先了解一下线程池的几个状态
线程池状态其实已经在 ThreadPoolExecutor
中解析的了
/**
* The main pool control state, ctl, is an atomic integer packing
* two conceptual fields
* workerCount, indicating the effective number of threads
* runState, indicating whether running, shutting down etc
*
* In order to pack them into one int, we limit workerCount to
* (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
* billion) otherwise representable. If this is ever an issue in
* the future, the variable can be changed to be an AtomicLong,
* and the shift/mask constants below adjusted. But until the need
* arises, this code is a bit faster and simpler using an int.
*
* The workerCount is the number of workers that have been
* permitted to start and not permitted to stop. The value may be
* transiently different from the actual number of live threads,
* for example when a ThreadFactory fails to create a thread when
* asked, and when exiting threads are still performing
* bookkeeping before terminating. The user-visible pool size is
* reported as the current size of the workers set.
*
* The runState provides the main lifecycle control, taking on values:
*
* RUNNING: Accept new tasks and process queued tasks
* SHUTDOWN: Don't accept new tasks, but process queued tasks
* STOP: Don't accept new tasks, don't process queued tasks,
* and interrupt in-progress tasks
* TIDYING: All tasks have terminated, workerCount is zero,
* the thread transitioning to state TIDYING
* will run the terminated() hook method
* TERMINATED: terminated() has completed
*
* The numerical order among these values matters, to allow
* ordered comparisons. The runState monotonically increases over
* time, but need not hit each state. The transitions are:
*
* RUNNING -> SHUTDOWN
* On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP
* On invocation of shutdownNow()
* SHUTDOWN -> TIDYING
* When both queue and pool are empty
* STOP -> TIDYING
* When pool is empty
* TIDYING -> TERMINATED
* When the terminated() hook method has completed
*
* Threads waiting in awaitTermination() will return when the
* state reaches TERMINATED.
*
* Detecting the transition from SHUTDOWN to TIDYING is less
* straightforward than you'd like because the queue may become
* empty after non-empty and vice versa during SHUTDOWN state, but
* we can only terminate if, after seeing that it is empty, we see
* that workerCount is 0 (which sometimes entails a recheck -- see
* below).
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
这里面其实解析得很清楚,使用了一个cas的原子整数的头三个位数来表示线程状态 (所以此时最大的线程数肯定是(2^29)-1
) 并且只用一个Int表示状态和数量的话,会比用两个int来表示会快(因为两个int表示就需要两次cas,而一个Int只需要一个或运算即可 具体看ctlOf()方法
)
各种状态如下表
场景
了解了池的状态之后,我们开始从一个场景去慢慢熟悉 shutdown
方法会有哪些坑
首先先上一段代码
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {
int count = i;
Future<?> future = pool.submit(() -> {
try {
log.info("begin count: {}", count);
Thread.sleep(1000);
log.info("end count: {}", count);
} catch (InterruptedException e) {
log.info("强制打断, count: {}", count);
}
});
}
Thread.sleep(1500);
pool.shutdown();
log.info("shutdown");
}
打印内容为
所以其实我们可以看到,执行了shutdown之后,如果线程3还在执行的途中或还在队列中时,此时是不会因为shutdown而中断线程3的。
然后再看下一段代码
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
CountDownLatch latch = new CountDownLatch(3);
for (int i = 1; i < 4; i++) {
int count = i;
Future<?> future = pool.submit(() -> {
try {
log.info("begin count: {}", count);
Thread.sleep(1000);
log.info("end count: {}", count);
} catch (InterruptedException e) {
log.info("强制打断, count: {}", count);
}
latch.countDown();
});
}
latch.await();
Thread.sleep(1500);
pool.shutdown();
log.info("shutdown");
}
打印内容为
具体原因
至于为什么会这样,我们主要根据之前说的线程状态,以及去看shutdown
方法的源码解析即可
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* <p>This method does not wait for previously submitted tasks to
* complete execution. Use {@link #awaitTermination awaitTermination}
* to do that.
*
* @throws SecurityException {@inheritDoc}
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置线程状态为shutdown
advanceRunState(SHUTDOWN);
// 中止空闲线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试去终结线程池,方法内部会用ctl去进行判断来是否终结线程池,若未执行完毕则不终结
tryTerminate();
}
其实方法内部已经解析得非常清楚,调用shutdown()方法的话,将已经提交的任务继续执行,不接收新任务。所以若是直接调用shutdown方法的话,其实是有点风险的,比如你想在线程执行完后再执行主线程的方法, 若主线程直接shutdown的话,不一定能控制住整体的线程
使用建议
那我们应该怎么去使用shutdown方法呢,对于内部我列了如下建议,当然这只是非常小的一部分,还有很多使用方法建议都直接去看看源码使用即可,有非常详细的介绍及说明。