前言
Today,我的人生导师大神强提了一个Question,简述为:使用自创建的线程跟使用线程池有啥区别?于是我脑海中闪现了创建线程池的那几个核心参数、工作流程、线程池的复用、拒绝机制、缓冲机制等,这些概念想必看文章你也理解(背)的滚瓜烂熟了。于是小心翼翼的表达了自己的观点,结果让我担心的事情还是发生了,老师傅又甩出来一段概念,简述为:线程池的线程支持shutdown,虚拟机进程接受到退出命令后线程池可以自行退出。于是脑海中又闪现了一堆概念,shutdown跟shutdownNow的区别、线程中断,虽然知道线程中断仅仅只是将线程的中断位设置为true,但是还是将此概念抛了出去,其实到此我还没get问题点,一根烟的功夫,突然灵光一现,我猜老师傅看了我使用的线程池相关的代码,我忽略了在特殊情况下的重启、宕机等等操作导致的任务丢失的问题(具体内容请参阅线程池的工作流程),尽管你从哪获取消息怎么落库,只要任务还在执行,或者队列中存在任务,这个时候都会丢失,而我记得当初使用了状态机,有些临界状态是没有容错机制的。
为什么中断不了?
虽然interrupt()方法是中断线程的没错,但是它也仅仅是将线程的中断位设置为true,它不会停止线程,而是需要用户自己去监视线程的状态为并做对应的处理。可能你会说stop()方法,这个方法已经被废弃掉了,这种显示的停止的方法带来的问题会更多,你可以想象一下会有哪些影响?言归正传,如果线程没有对中断进行处理,仅仅是调用interrupt()是不会停止的,如下。
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Error");
}else {
System.out.println("Success");
}
}
});
thread.start();
thread.interrupt();
}
//题外话:此代码会不断输出Error,如果要解决这个问题,响应中断或者直接return掉即可。
如何解决任务丢失?
从前面说的这么多,现在看来,抛开直接KILL掉程序不说,首先我们要保证程序在正常的重启期间,任务是不能丢失的,笔者首先想到是实现Hook线程方法,也就是钩子方法,在程序关闭的时候触发收尾工作,来保证线程池的正常关闭。老师傅说可以交给Spring管理即可,思考了一下也是,我们只需要实现destroy方法,然后shutdown即可。既然想到了Spring,那Spring的线程池是否处理了shutdown方法,那我们来探究一下,日常使用Spring线程池的时候最常见的一行配置你肯定见过,如下。
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
3 <!-- 线程池维护线程的最少数量 -->
4 <property name="corePoolSize" value="5" />
5 <!-- 允许的空闲时间 -->
6 <property name="keepAliveSeconds" value="200" />
7 <!-- 线程池维护线程的最大数量 -->
8 <property name="maxPoolSize" value="10" />
9 <!-- 缓存队列 -->
10 <property name="queueCapacity" value="20" />
11 <!-- 对拒绝task的处理策略 -->
12 <property name="rejectedExecutionHandler">
13 <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
14 </property>
15 </bean>
简单粗暴,我们直接来看ThreadPoolTaskExecutor,如下
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
public void setCorePoolSize(int corePoolSize) {//...}
public int getCorePoolSize() {//...}
public void setMaxPoolSize(int maxPoolSize) {//...}
//...
}
相信大部分童鞋看到这里就明白了,InitializingBean跟DisposableBean这不经常见么,是的没错,凡是继承InitializingBean跟DisposableBean的Bean都会在bean的初始化跟销毁的时候调用对应的afterPropertiesSet()跟destory()方法。具体直接上一张图搞定。
然后回过头来,我们来看一下ExecutorConfigurationSupport
初始化方法暂且抛开不说,因为我们这里探究的是销毁方法。直接上代码。
public void destroy() {
this.shutdown();
}
public void shutdown() {
if (this.logger.isInfoEnabled()) {
this.logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
//waitForTasksToCompleteOnShutdown为被调用时是否等待当前任务完成
if (this.waitForTasksToCompleteOnShutdown) {
//如果需要等待任务执行玩笑,就调用shutdown方法
this.executor.shutdown();
} else {
//如果不需求,就强制关闭,并会返回那些未执行的任务
Iterator var1 = this.executor.shutdownNow().iterator();
while(var1.hasNext()) {
Runnable remainingTask = (Runnable)var1.next();
//取消所有剩下需要执行的线程
this.cancelRemainingTask(remainingTask);
}
}
//如果设置了等待时长,当前线程阻塞直到如下3种情况
//1:等所有已提交的任务(包括正在运行的和队列中等待的)执行完毕
//2:超过设置时长
//3:线程被中断,响应InterruptedException
this.awaitTerminationIfNecessary(this.executor);
}
}
由此上我们可以看到,使用Spring线程池可以最大程度上解决任务丢失的问题,当然我们可以通过实现DisposableBean接口来自定义销毁的操作。那么还有更优雅的处理方式吗?
优雅的关闭线程池操作
我相信很多童鞋都用过Guava,Guava中包含了许多并发类,同时也包含了几个方便的线程池相关的ExecuorService的实现,但需要注意的是这些实现类都无法通过直接创建或者子类化来创建实例,但是我们可以通过MoreExecuors来创建它们,MoreExecuors中的方法如下
笔者英语小学水平,直接上Google翻译图
依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
getExitingExecutorService
//获得一个跟随JVM关闭而关闭的线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(20), new ThreadPoolExecutor.CallerRunsPolicy());
ExecutorService executorService = MoreExecutors.getExitingExecutorService(threadPoolExecutor);
getExitingExecutorService源码分析
//关键源码
final ExecutorService getExitingExecutorService(ThreadPoolExecutor executor) {
//120S为关闭等待时长
return this.getExitingExecutorService(executor, 120L, TimeUnit.SECONDS);
}
final ExecutorService getExitingExecutorService(ThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) {
//改变ThreadFactory,为它设置守护线程,
MoreExecutors.useDaemonThreadFactory(executor);
//封装传入的线程池
ExecutorService service = Executors.unconfigurableExecutorService(executor);
//设置钩子线程,并设置等待时长
this.addDelayedShutdownHook(service, terminationTimeout, timeUnit);
return service;
}
final void addDelayedShutdownHook(final ExecutorService service, final long terminationTimeout, final TimeUnit timeUnit) {
Preconditions.checkNotNull(service);
Preconditions.checkNotNull(timeUnit);
this.addShutdownHook(MoreExecutors.newThread("DelayedShutdownHook-for-" + service, new Runnable() {
public void run() {
try {
//关闭线程池
service.shutdown();
//这个需要等到提交的任务全部执行完,这个上文跟Spring的阻塞等待关闭是一个道理
service.awaitTermination(terminationTimeout, timeUnit);
} catch (InterruptedException var2) {
}
}
}));
}
addDelayedShutdownHook
//方法使用
MoreExecutors.addDelayedShutdownHook(threadPoolExecutor,120L,TimeUnit.SECONDS);
//关键源码,此处调用的addDelayedShutdownHook跟上面的一样,只不过这里没有改变ThreadFactory,只设置了钩子线程
public static void addDelayedShutdownHook(ExecutorService service, long terminationTimeout, TimeUnit timeUnit) {
(new MoreExecutors.Application()).addDelayedShutdownHook(service, terminationTimeout, timeUnit);
}
shutdownAndAwaitTermination
//使用方法
MoreExecutors.shutdownAndAwaitTermination(threadPoolExecutor,10L,TimeUnit.SECONDS);
//关键源码
public static boolean shutdownAndAwaitTermination(ExecutorService service, long timeout, TimeUnit unit) {
long halfTimeoutNanos = unit.toNanos(timeout) / 2L;
//预关闭
service.shutdown();
try {
//判断是否有提交的任务没有执行完。
if (!service.awaitTermination(halfTimeoutNanos, TimeUnit.NANOSECONDS)) {
//如果没有执行完,继续尝试强行关闭
service.shutdownNow();
//然后进行阻塞等待关闭
service.awaitTermination(halfTimeoutNanos, TimeUnit.NANOSECONDS);
}
} catch (InterruptedException var7) {
//如果在关闭过程中如果发生中断,先设置中断恢复。
Thread.currentThread().interrupt();
//直接强行关闭
service.shutdownNow();
}
//返回状态
return service.isTerminated();
}
从上面分析来看,MoreExecutors处理的更为严谨一点,比Spring更为灵活,那么我们该如何使用呢?下面笔者提供两个Demo作为参考。
//1
@Configuration
public class ThreadPoolConfiguration {
@Bean(name = "executorServiceExiting")
public ExecutorService executorService(){
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5, 10, 5L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(20), new ThreadPoolExecutor.CallerRunsPolicy());
return MoreExecutors.getExitingExecutorService(threadPoolExecutor);
}
@Bean(name = "executorServiceShutdownHook")
public ExecutorService executorServiceShutdownHook(){
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5, 10, 5L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(20), new ThreadPoolExecutor.CallerRunsPolicy());
MoreExecutors.addDelayedShutdownHook(threadPoolExecutor,120L,TimeUnit.SECONDS);
return threadPoolExecutor;
}
}
//2
@Configuration
public class ThreadPoolConfiguration {
@Bean
@Order(2)
public ExecutorService executorService(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(20), new ThreadPoolExecutor.CallerRunsPolicy());
ThreadPoolRegisterCenter.register(threadPoolExecutor);
return threadPoolExecutor;
}
}
//-------------
/**
* @author Duansg
* @desc 线程池的注册中心
* @date 2020-04-01 00:23:12
*/
@Configuration
@Order(1)
public class ThreadPoolRegisterCenter implements ApplicationRunner {
/**
* @desc 线程池容器
*/
private static final Set<ExecutorService> THREAD_POOL_CONTEXT = Sets.newConcurrentHashSet();
/**
* @desc 添加线程池到容器中
* @param executorService
*/
public static void register(ExecutorService executorService) {
THREAD_POOL_CONTEXT.add(executorService);
}
@Override
public void run(ApplicationArguments args) throws Exception {
//设置钩子方法,并使用shutdownAndAwaitTermination方法进行关闭处理
Runtime.getRuntime().addShutdownHook(new Thread(() -> THREAD_POOL_CONTEXT.forEach(executorService -> {
if (!ObjectUtils.isEmpty(executorService)){
MoreExecutors.shutdownAndAwaitTermination(executorService,10L, TimeUnit.SECONDS);
}
})));
}
}