溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Spring Boot中利用@Async實現異步調用

發布時間:2021-06-07 17:59:53 來源:億速云 閱讀:213 作者:Leah 欄目:編程語言

本篇文章為大家展示了怎么在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實現異步調用,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女