当前位置: 首页 > news >正文

java非异步线程池_Spring Boot利用@Async异步调用:ThreadPoolTaskScheduler线程池的优雅关闭详解...

前言

之前分享了一篇关于Spring Boot中使用@Async来实现异步任务和线程池控制的文章:《Spring Boot使用@Async实现异步调用:自定义线程池》。由于最近身边也发现了不少异步任务没有正确处理而导致的不少问题,所以在本文就接前面内容,继续说说线程池的优雅关闭,主要针对ThreadPoolTaskScheduler线程池。

问题现象

在上篇文章的例子Chapter4-1-3中,我们定义了一个线程池,然后利用@Async注解写了3个任务,并指定了这些任务执行使用的线程池。在上文的单元测试中,我们没有具体说说shutdown相关的问题,下面我们就来模拟一个问题现场出来。

第一步:如前文一样,我们定义一个ThreadPoolTaskScheduler线程池:

@SpringBootApplication

public class Application {

public static void main(String[] args) {

SpringApplication.run(Application.class, args);

}

@EnableAsync

@Configuration

class TaskPoolConfig {

@Bean("taskExecutor")

public Executor taskExecutor() {

ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();

executor.setPoolSize(20);

executor.setThreadNamePrefix("taskExecutor-");

return executor;

}

}

}

第二步:改造之前的异步任务,让它依赖一个外部资源,比如:Redis

@Slf4j

@Component

public class Task {

@Autowired

private StringRedisTemplate stringRedisTemplate;

@Async("taskExecutor")

public void doTaskOne() throws Exception {

log.info("开始做任务一");

long start = System.currentTimeMillis();

log.info(stringRedisTemplate.randomKey());

long end = System.currentTimeMillis();

log.info("完成任务一,耗时:" + (end - start) + "毫秒");

}

@Async("taskExecutor")

public void doTaskTwo() throws Exception {

log.info("开始做任务二");

long start = System.currentTimeMillis();

log.info(stringRedisTemplate.randomKey());

long end = System.currentTimeMillis();

log.info("完成任务二,耗时:" + (end - start) + "毫秒");

}

@Async("taskExecutor")

public void doTaskThree() throws Exception {

log.info("开始做任务三");

long start = System.currentTimeMillis();

log.info(stringRedisTemplate.randomKey());

long end = System.currentTimeMillis();

log.info("完成任务三,耗时:" + (end - start) + "毫秒");

}

}

注意:这里省略了pom.xml中引入依赖和配置redis的步骤

第三步:修改单元测试,模拟高并发情况下ShutDown的情况:

@RunWith(SpringJUnit4ClassRunner.class)

@SpringBootTest

public class ApplicationTests {

@Autowired

private Task task;

@Test

@SneakyThrows

public void test() {

for (int i = 0; i < 10000; i++) {

task.doTaskOne();

task.doTaskTwo();

task.doTaskThree();

if (i == 9999) {

System.exit(0);

}

}

}

}

说明:通过for循环往上面定义的线程池中提交任务,由于是异步执行,在执行过程中,利用System.exit(0)来关闭程序,此时由于有任务在执行,就可以观察这些异步任务的销毁与Spring容器中其他资源的顺序是否安全。

第四步:运行上面的单元测试,我们将碰到下面的异常内容。

org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:204) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:348) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:129) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:194) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at org.springframework.data.redis.core.RedisTemplate.randomKey(RedisTemplate.java:781) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

at com.didispace.async.Task.doTaskOne(Task.java:26) ~[classes/:na]

at com.didispace.async.Task$$FastClassBySpringCGLIB$$ca3ff9d6.invoke() ~[classes/:na]

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.14.RELEASE.jar:4.3.14.RELEASE]

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE]

at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE]

at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_151]

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_151]

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_151]

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_151]

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_151]

at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]

Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

at redis.clients.util.Pool.getResource(Pool.java:53) ~[jedis-2.9.0.jar:na]

at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226) ~[jedis-2.9.0.jar:na]

at redis.clients.jedis.JedisPool.getResource(JedisPool.java:16) ~[jedis-2.9.0.jar:na]

at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:194) ~[spring-data-redis-1.8.10.RELEASE.jar:na]

... 19 common frames omitted

Caused by: java.lang.InterruptedException: null

at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014) ~[na:1.8.0_151]

at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2088) ~[na:1.8.0_151]

at org.apache.commons.pool2.impl.LinkedBlockingDeque.pollFirst(LinkedBlockingDeque.java:635) ~[commons-pool2-2.4.3.jar:2.4.3]

at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442) ~[commons-pool2-2.4.3.jar:2.4.3]

at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:361) ~[commons-pool2-2.4.3.jar:2.4.3]

at redis.clients.util.Pool.getResource(Pool.java:49) ~[jedis-2.9.0.jar:na]

... 22 common frames omitted

如何解决

原因分析

从异常信息JedisConnectionException: Could not get a resource from the pool来看,我们很容易的可以想到,在应用关闭的时候异步任务还在执行,由于Redis连接池先销毁了,导致异步任务中要访问Redis的操作就报了上面的错。所以,我们得出结论,上面的实现方式在应用关闭的时候是不优雅的,那么我们要怎么做呢?

解决方法

要解决上面的问题很简单,Spring的ThreadPoolTaskScheduler为我们提供了相关的配置,只需要加入如下设置即可:

@Bean("taskExecutor")

public Executor taskExecutor() {

ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();

executor.setPoolSize(20);

executor.setThreadNamePrefix("taskExecutor-");

executor.setWaitForTasksToCompleteOnShutdown(true);

executor.setAwaitTerminationSeconds(60);

return executor;

}

说明:setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。

完整示例:

读者可以根据喜好选择下面的两个仓库中查看Chapter4-1-4项目:

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章:

  • mongo java driver 3.2_MongoDB-JAVA-Driver 3.2版本常用代碼全整理(2) - 查詢
  • java中标记怎么用_在Java中使用标记(标签)
  • java结束sql链接_数据查询时报出java.sql.SQLException: 关闭的连接
  • java 控制台画表格_Java库在控制台上构建和打印表格?
  • 242. valid anagram java_Leetcode242 Valid Anagram JAVA语言
  • java 查找大写字母_Java实现给定一个包含大写字母和小写字母的字符串,找到通过这些...
  • java 绘图球的移动_在Java上绘制2个朝不同方向移动的球,但一个消失了
  • php中购物车结算代码,jquery购物车结算功能实现方法
  • php.ini配置 耗时,配置PHP.INI监测服务器的脚本耗时
  • java自动生成测试与评估,jmeter如何自动生成测试报告
  • php memcached存储对象,从memcached获取对象并在PHP中设置为self
  • java阅读安卓,java – 如何在android中逐行阅读?
  • plotm matlab,MATLAB画地图的工具:worldmap和m_map
  • matlab不能盗版吗,matlab为了防止盗版,会不会篡改程序运行结果?这是明证
  • matlab 复权数据,〖Matlab〗基于通达信股价数据的复权处理(fantuanxiaot版本)
  • 自己简单写的 事件订阅机制
  • 【剑指offer】让抽象问题具体化
  • canvas绘制圆角头像
  • css系列之关于字体的事
  • express + mock 让前后台并行开发
  • JavaScript中的对象个人分享
  • Java反射-动态类加载和重新加载
  • MySQL主从复制读写分离及奇怪的问题
  • rabbitmq延迟消息示例
  • React+TypeScript入门
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • Sublime text 3 3103 注册码
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 编写高质量JavaScript代码之并发
  • 大整数乘法-表格法
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 浅谈web中前端模板引擎的使用
  • 通信类
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (9)目标检测_SSD的原理
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (规划)24届春招和25届暑假实习路线准备规划
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (一)基于IDEA的JAVA基础1
  • (译) 函数式 JS #1:简介
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转) RFS+AutoItLibrary测试web对话框
  • (转载)(官方)UE4--图像编程----着色器开发
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Core 中的路径问题
  • .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • @RequestMapping处理请求异常
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析
  • [AX]AX2012开发新特性-禁止表或者表字段