前言
使用自创建的线程跟使用线程池有啥区别?提到线程池是不是脑海中闪现了创建线程池的那几个核心参数、工作流程、线程池的复用、拒绝机制、缓冲机制等,这些理论知识点想必也牢记许久了。虽然线程池支持在虚拟机进程接受到退出命令后可以进行 shutdown。那么 shutdown 跟线程中断又有什么区别?在运行中的线程能否直接 kill 掉?我们能否监听关闭事件进行补偿?
为什么线程中断不了?
回答这个问题之前需要先思考,中断会有什么影响?如果直接 kill 掉,那么这个线程使用的所有资源能被正常释放吗?我们虽然 interrupt()方法是中断线程的没错,但是它也仅仅是将线程的中断位设置为 true,它不会停止线程,而是需要用户自己去监视线程的状态为并做对应的处理。可能你会说 stop()方法,这个方法已经被废弃掉了,这种显示的停止的方法带来的问题会更多,你可以想象一下会有哪些影响?言归正传,如果线程没有对中断进行处理,仅仅是调用 interrupt()是不会停止的,如下。
1 | public static void main(String[] args) { |
如何解决任务丢失?
前面说了这么多,现在看来,抛开直接 KILL 掉程序不说,首先我们要保证程序在正常的重启期间,任务是不能丢失的,你可能先想到是实现 Hook 方法,在程序关闭的时候触发收尾工作,来保证线程池的正常关闭。老师傅说可以交给 Spring 管理即可,思考了一下也是,我们只需要实现 destroy 方法,然后 shutdown 即可。既然想到了 Spring,那 Spring 的线程池是否处理了 shutdown 方法,那我们来探究一下,日常使用 Spring 线程池的时候最常见的一行配置你肯定见过,如下。
1 | <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> |
ThreadPoolTaskExecutor
1 | public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor { |
InitializingBean 跟 DisposableBean 这不经常见么,是的没错,凡是继承 InitializingBean 跟 DisposableBean 的 Bean,会在 bean 的初始化跟销毁的时候调用对应的 afterPropertiesSet()跟 destory()方法。具体直接上一张图搞定。
ExecutorConfigurationSupport
初始化方法暂且抛开不说,因为我们这里探究的是销毁方法。直接上代码。
1 | public void destroy() { |
由此上我们可以看到,使用 Spring 线程池可以最大程度上解决任务丢失的问题,当然我们可以通过实现 DisposableBean 接口来自定义销毁的操作。那么还有更优雅的处理方式吗?
Guava
我相信很多童鞋都用过 Guava,Guava 中包含了许多并发类,同时也包含了几个方便的线程池相关的 ExecuorService 的实现,但需要注意的是这些实现类都无法通过直接创建或者子类化来创建实例,但是我们可以通过 MoreExecuors 来创建它们,MoreExecuors 中的方法如下
依赖
1 | <dependency> |
getExitingExecutorService
1 | //获得一个跟随JVM关闭而关闭的线程池 |
源码分析
1 | //关键源码 |
1 | final void addDelayedShutdownHook(final ExecutorService service, final long terminationTimeout, final TimeUnit timeUnit) { |
addDelayedShutdownHook
1 | //方法使用 |
源码分析
1 | //关键源码,此处调用的addDelayedShutdownHook跟上面的一样,只不过这里没有改变ThreadFactory,只设置了钩子线程 |
shutdownAndAwaitTermination
1 | //使用方法 |
源码分析
1 |
|
Example-Demo
1 | //1 |
1 | //2 |