这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
前言
线程池我们平时项目中都会用到,也是面试过程中必问的知识点,但是我们很少注意到线程要如何做安全关闭,常常导致生产环境的一些问题,这些情况定位起来比较麻烦因为没有打印日志,我们可能统一归结为发布异常。 其实这一块的内容挺多,优雅发布,优雅停机…等待.
线程池系列
Java并发编程-线程池(一)
Java并发编程-线程池源码分析(二)
Java并发编程-JDK线程池和Spring线程池(三)
1. 案例
例子比较简单, 定义一个Spring线程池, 线程池里面task调用了一个订单的服务, 订单服务保存到缓存里面,然后打印。
1.1 线程池配置
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor createThreadPoolTaskExecutor () {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("xx定时任务");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
复制代码
1.2 订单对象和订单服务
@Data
public class OrderDto {
private String orderNo;
private Long goodsId;
}
@Service
public class OrderService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@SneakyThrows
public void saveOrder() {
OrderDto orderDto = new OrderDto();
orderDto.setOrderNo("No2021081501");
orderDto.setGoodsId(1L);
System.out.println("开始保存订单信息:");
stringRedisTemplate.opsForValue().set("No2021081501", JSON.toJSONString(orderDto));
System.out.println("保存订单信息成功");
String orderInfo = stringRedisTemplate.opsForValue().get("No2021081501");
System.out.println("orderInfo:" + orderInfo);
}
}
复制代码
1.3 单元测试
@Service
public class OrderService {
@Resource
private StringRedisTemplate stringRedisTemplate;
public void saveOrder() {
OrderDto orderDto = new OrderDto();
orderDto.setOrderNo("No2021081501");
orderDto.setGoodsId(1L);
System.out.println("开始保存订单信息:");
stringRedisTemplate.opsForValue().set("No2021081501", JSON.toJSONString(orderDto));
System.out.println("保存订单信息成功");
String orderInfo = stringRedisTemplate.opsForValue().get("No2021081501");
System.out.println("orderInfo:" + orderInfo);
}
}
复制代码
1.4 运行结果
到保存订单信息就断了,没有往下执行,也没有异常,按道理应该打印订单信息.
打个断点进去看下,其实主线程单元测试结束容器bean实例已经释放了,所以会报链接不存在。
1.5 如何解决
解决的方案也很简单在线程池配置的时候配置参数
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
复制代码
在执行测试案例已经和预期结果一致了。
线程池安全关闭原理分析
2.1 终止线程的4种方式
- 正常运行结束
- 使用退出标志退出线程
- Interrupt 方法结束线程、
- stop 方法终止线程(线程不安全),程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,
2.2 线程池提供的shutdown和shutdownnow方法
2.2.1 shutdown 主要流程
调用线程池的shuwdown方法时,
- 如果线程正在执行线程池里的任务,即便任务处于阻塞状态,线程也不会被中断,而是继续执行。
- 如果线程池阻塞等待从队列里读取任务,则会被唤醒,但是会继续判断队列是否为空,如果不为空会继续从队列里读取任务,为空则线程退出。
2.2.2 shutdownnow 主要流程
调用线程池的shutdownNow
1)如果线程正在getTask方法中执行,则会通过for循环进入到if语句,于是getTask返回null,从而线程退出。不管线程池里是否有未完成的任务。
2) 如果线程因为执行提交到线程池里的任务而处于阻塞状态,则会导致报错(如果任务里没有捕获InterruptedException异常),否则线程会执行完当前任务,然后通过getTask方法返回为null来退出。
shutdown和shutdown主要区别
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
2.3 Spring 线程池关闭的设置
看下Spring线程池关闭的源码shutdown 方法
public void shutdown() {
if (logger.isDebugEnabled()) {
logger.debug("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
// 是否需要等所有的任务都结束
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
for (Runnable remainingTask : this.executor.shutdownNow()) {
cancelRemainingTask(remainingTask);
}
}
//
awaitTerminationIfNecessary(this.executor);
}
}
复制代码
awaitTerminationIfNecessary 方法
private void awaitTerminationIfNecessary(ExecutorService executor) {
if (this.awaitTerminationMillis > 0) {
try {
if (!executor.awaitTermination(this.awaitTerminationMillis, TimeUnit.MILLISECONDS)) {
if (logger.isWarnEnabled()) {
logger.warn("Timed out while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
}
}
catch (InterruptedException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Interrupted while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
Thread.currentThread().interrupt();
}
}
}
复制代码
最后spring线程池 waitForTasksToCompleteOnShutdown和awaitTerminationMillis需要同时设置,否则会不生效. 当然说了这么多如果系统宕机或者运维同学kill -9 pid的啥安全关闭或者优雅停机都无从谈起。