在上一篇博客中,我使用了线程池进行管理线程,达到线程复用的效果。
详情参考:线程池+CountDownLatch优化代码,提高程序执行效率
程序启动、运行皆无异常,线程池确实对程序中创建的线程进行管理,但是,在我关闭tomcat时,无法正常关闭,程序出现报错。报错信息如下:
05-Apr-2020 19:09:45.003 璀﹀憡 [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads
The web application [ROOT] appears to have started a thread named [pool-5-thread-2] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:492)
java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:680)
java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java.lang.Thread.run(Thread.java:748
注意看这行报错信息:
The web application [ROOT] appears to have started a thread named [pool-5-thread-2] but has failed to stop it. This is very likely to create a memory leak.
异常信息显示: 应用程序启动了一个名为pool-5-thread-2的线程,但是关闭失败,这很可能导致内存泄漏。
很明显,tomcat没能关掉ThreadPoolExecutor的核心线程,因此需要在关闭tomcat前手动关闭。
如何手动关闭线程池请参考:停止Tomcat webapp 线程池报错的尝试解决
那如果我不想手动关闭线程池怎么办?
使用Spring中中的 ThreadPoolTaskExecutor。将线程池的关闭交给Spring去管理。
在applicationContext.xml中加入
程序中:
@Autowired
private ThreadPoolTaskExecutor threadPool;
threadPool.execute(() -> {
model.addAttribute("types", typeService.findHottestType(6));
countDownLatch.countDown();
});
threadPool.execute(() -> {
model.addAttribute("tags", tagService.findHottestTag(6));
countDownLatch.countDown();
});
threadPool.execute(() -> {
model.addAttribute("blogs", blogService.findHotestBlogs());
countDownLatch.countDown();
});
threadPool.execute(() -> {
model.addAttribute("newBlogs", blogService.findNewestBlogs(8));
countDownLatch.countDown();
});
再次启动程序,测试,关闭tomcat,报错信息不再提示。
如何优雅安全的关闭线程池?
线程池关闭不合理,导致应用⽆法正常stop的情况,这是我们很容易忽略的。
下面就以ThreadPoolExecutor为例,来介绍下如何优雅的关闭线程池。
线程中断
在介绍线程池关闭之前,先介绍下Thread的interrupt。
在程序中,我们是不能随便中断⼀个线程的,因为这是极其不安全的操作,我们⽆法知道这个线程正运⾏在什么状态,它可能持有某把锁,强⾏中断可能导致锁不能释放的问题;或者线程可能在操作数据库,强⾏中断导致数据不一致,从而混乱的问题。正因此,Java⾥将Thread的stop⽅法设置为过时,以禁⽌⼤家使⽤。
⼀个线程什么时候可以退出呢?当然只有线程自⼰才能知道。
所以我们这⾥要说的Thread的interrrupt⽅法,本质不是⽤来中断一个线程。而是将线程设置⼀个中断状态。当我们调⽤线程的interrupt方法,它有两个作⽤用:
1、如果此线程处于阻塞状态(比如调⽤了wait方法,io等待),则会立刻退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做⼀定的处理,然后让线程退出。
2、如果此线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以线程要在适当的位置通过调用isInterrupted方法来查看自⼰是否被中断,并做退出操作。
注:如果线程的interrupt方法先被调用,然后线程调用阻塞方法进入阻塞状态,InterruptedException异常依旧会抛出。如果线程捕获InterruptedException异常后,继续调用阻塞方法, 将不再触发InterruptedException异常。
线程池的关闭
线程池提供了两个关闭方法,shutdownNow和shuwdown⽅法。
shutdownNow⽅法的解释是:线程池拒接收新提交的任务,同时⽴刻关闭线程池,线程池里的任务不再执行。
shutdown⽅法的解释是:线程池拒接收新提交的任务,同时等待线程池⾥的任务执行完毕后关闭线程池。
以上的说法虽然没错,但是还有很多的细节问题,比如调用shutdown⽅法后,正在执⾏的任务的线程会做出什么反应?正在等待任务的线程又会做出什么反应?线程在什么情况下才会彻底退出。如果不了解这些细节,在关闭线程池时就难免遇到,像线程池关闭不了,关闭线程池出现报错等情况。
再说这些关闭线程池细节问题之前,需要强调一点的是,调用完shutdownNow和shuwdown⽅法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调⽤awaitTermination⽅法进⾏同步等待。
我们来看看shutdownNow和shuwdown⽅法的源码:
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.
*
* 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();
// 此方法在ThreadPoolExecutor中是空实现,具体实现在其子类ScheduledThreadPoolExecutor
// 中,用于取消延时任务。
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试将线程池置为TERMINATED状态
tryTerminate();
}
shutdownNow:
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution. These tasks are drained (removed)
* from the task queue upon return from this method.
*
* This method does not wait for actively executing tasks to
* terminate. Use {@link #awaitTermination awaitTermination} to
* do that.
*
*
There are no guarantees beyond best-effort attempts to stop
* processing actively executing tasks. This implementation
* cancels tasks via {@link Thread#interrupt}, so any task that
* fails to respond to interrupts may never terminate.
*
* @throws SecurityException {@inheritDoc}
*/
public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查方法调用方是否用权限关闭线程池以及中断工作线程
checkShutdownAccess();
// 将线程池运行状态置为STOP
advanceRunState(STOP);
// 中断所有线程,包括正在运行的线程
interruptWorkers();
// 将未执行的任务移入列表中
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试将线程池置为TERMINATED状态
tryTerminate();
return tasks;
}
具体源码解释请参考:关闭线程池的正确姿势