一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring Boot利用@Async異步調用:ThreadPoolTaskScheduler線程池的優雅關閉詳解

Spring Boot利用@Async異步調用:ThreadPoolTaskScheduler線程池的優雅關閉詳解

2021-04-25 11:35翟永超 Java教程

這篇文章主要給大家介紹了關于Spring Boot利用@Async異步調用:ThreadPoolTaskScheduler線程池的優雅關閉的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧

前言

之前分享了一篇關于Spring Boot中使用@Async來實現異步任務和線程池控制的文章:《Spring Boot使用@Async實現異步調用:自定義線程池》。由于最近身邊也發現了不少異步任務沒有正確處理而導致的不少問題,所以在本文就接前面內容,繼續說說線程池的優雅關閉,主要針對ThreadPoolTaskScheduler線程池。

問題現象

在上篇文章的例子Chapter4-1-3中,我們定義了一個線程池,然后利用@Async注解寫了3個任務,并指定了這些任務執行使用的線程池。在上文的單元測試中,我們沒有具體說說shutdown相關的問題,下面我們就來模擬一個問題現場出來。

第一步:如前文一樣,我們定義一個ThreadPoolTaskScheduler線程池:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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的情況:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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容器中其他資源的順序是否安全。

第四步:運行上面的單元測試,我們將碰到下面的異常內容。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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為我們提供了相關的配置,只需要加入如下設置即可:

?
1
2
3
4
5
6
7
8
9
@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項目:

Github:https://github.com/dyc87112/SpringBoot-Learning/

Gitee:https://gitee.com/didispace/SpringBoot-Learning/

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:http://blog.didispace.com/springbootasync-3/

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本在线观看免费高清 | pppd在线播放 | 小便japanesewctv | 好吊操这里有精品 | 国内精品伊人久久大香线焦 | 四虎黄色影视库 | 吉泽明步高清无码中文 | 亚洲欧美综合在线观看 | 国产欧美日韩在线不卡第一页 | 久久精品99国产精品日本 | 紧身短裙女教师波多野 | 四虎永久网址影院 | 国产一区二区免费视频 | 特黄未满14周岁毛片 | 亚洲卡一卡2卡三卡4麻豆 | 国产视频一二三区 | 天天做天天爰夜夜爽 | 精品亚洲视频在线观看 | 精品网站一区二区三区网站 | 双性总裁被调教1v1 双性双根 | 三级黄片毛片 | 亚洲国产综合久久久无码色伦 | 精品一区二区免费视频蜜桃网 | 成年人在线观看视频免费 | 精品一区二区三区视频日产 | 国产精品一区二区三区免费 | 欧美整片在线 | 精品亚洲国产一区二区 | 校花在公车上被内射好舒 | 色综合天天综合网国产人 | 成人特级毛片69免费观看 | xxxx野外性xxxx | 2021日本三级理论影院 | 潘甜甜在线观看 | 午夜欧美福利视频 | 调教麻麻成贱m | 高清国产激情视频在线观看 | 9总探花新品牛仔背带裤 | 阿 好深 快点 老师受不了 | 91一个人的在线观看www | fc2成人免费共享视频 |