本篇文章为大家展示了怎么在Spring Boot中利用@Async实现异步调用,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
定义一个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(<generated>) ~[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),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
上述内容就是怎么在Spring Boot中利用@Async实现异步调用,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。