springboot实现异步调用@Async
介紹
所謂的異步執行其實就是使用多線程的方式實現異步調用
異步有什么好處呢?
如果一個業務邏輯執行完成需要多個步驟,也就是調用多個方法去執行,
這個時候異步執行比同步執行相應更快。不過要注意異步請求的順序和處理結果的順序最好一致,不然就達不到效果了
注意事項
1、在默認情況下,未設置TaskExecutor時,默認是使用SimpleAsyncTaskExecutor這個線程池,但此線程不是真正意義上的線程池,因為線程不重用,每次調用都會創建一個新的線程??赏ㄟ^控制臺日志輸出可以看出,每次輸出線程名都是遞增的。所以最好我們來自定義一個線程池。
2、調用的異步方法,不能為同一個類的方法(包括同一個類的內部類),簡單來說,因為Spring在啟動掃描時會為其創建一個代理類,而同類調用時,還是調用本身的代理類的,所以和平常調用是一樣的。其他的注解如@Cache等也是一樣的道理,說白了,就是Spring的代理機制造成的。所以在開發中,最好把異步服務單獨抽出一個類來管理。下面會重點講述
什么情況下會導致@Async異步方法會失效?
1.不要在本類中異步調用。即一個方法是異步方法,然后用另一個方法調用這個異步方法。
2.不要有返回值,使用void
3.不能使用本類的私有方法或者非接口化加注@Async,因為代理不到失效
4.異步方法不能使用static修飾
5.異步類需使用@Component注解,不然將導致spring無法掃描到異步類
6.SpringBoot框架必須在啟動類中增加@EnableAsync注解
7.異步方法不要和事物注解同時存在。可以在事物的方法中調用另外一個類中的異步方法。在調用Async方法的方法上標注@Transactional是管理調用方法的事務的,在Async方法上標注@Transactional是管理異步方法的事務,事務因線程隔離
8.諸如以上幾點的情況比如spring中的@Transactional還有cache注解也不能有以上幾點情況,否則也會失效的,因為本質都是因為代理的機制導致的
定義一個線程池
@Configuration
public class AsyncTaskPoolConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-ws-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
PS:使用@Async 注解時,需要注意一下幾點:
當項目中只有一個線程池時,我們只需要寫 @Async 即可將需要異步的方法進行異步;
當項目中存在多個線程池,我們在寫異步時,需要注意如果只寫@Async注解沒有任何屬性則將此方法的執行異步到帶有 @Primary 注解修飾的線程池中執行。
還可以將方法異步到指定的線程池中,如 @Async(“threadPool”)則是將此方法異步到threadPool 線程池中執行。
package com.xsrt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors()*2);
taskExecutor.setMaxPoolSize(60);
taskExecutor.setQueueCapacity(20000);
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setThreadGroupName("Task-");
taskExecutor.setThreadNamePrefix("Async-");
taskExecutor.setBeanName("threadPoolTaskExecutor");
return taskExecutor;
}
}
如果代碼中需要多個線程池,可以按照如下方式配置
package com.xsrt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("threadPoolTaskExecutor")
@Primary //指定當前線程池為主線程池
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors()*2);
taskExecutor.setMaxPoolSize(60);
taskExecutor.setQueueCapacity(2000);
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setThreadGroupName("Task-");
taskExecutor.setThreadNamePrefix("Async-");
taskExecutor.setBeanName("threadPoolTaskExecutor");
return taskExecutor;
}
@Bean("threadPool")//其他線程池
public ThreadPoolTaskExecutor threadPool(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors()*2);
taskExecutor.setMaxPoolSize(60);
taskExecutor.setQueueCapacity(200);
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setThreadGroupName("Task-");
taskExecutor.setThreadNamePrefix("Pool-");
taskExecutor.setBeanName("threadPoolTaskExecutor");
return taskExecutor;
}
}
異步方法調用如下
package com.ccbobe.websocket.async;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class AsyncService {
@Async
public String asyncPrimary(){
log.info("執行 Primary 異步......");
return null;
}
@Async("threadPool")
public String asyncPools(){
log.info("執行 threadPool 異步......");
return null;
}
}
使用@Async和@EnableAsync注解
首先使用@EnableAsync注解開啟異步調用功能,該注解可以放置的位置有:
啟動類上方
@EnableAsync
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
調用異步的當前類上方
@EnableAsync
@RestController
public class TestAsyncController(){}
在配置類上方使用
@Configuration
@EnableAsync
public class AsyncTaskPoolConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-ws-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
編寫異步請求
在異步執行的方法上添加注解:@Async
Component
@Log4j2
public class AsyncTask {
//這里注入的是dubbo的服務,和異步請求沒有多大關系
@Reference(check = false)
private AuthorFacade authorFacade;
/**
* 獲取作者信息
*
* @param authorId 作者ID
* @return 作者信息
*/
@Async("taskExecutor")
public Future<AuthorDTO> getAuthor(String authorId){
try {
System.out.println("執行線程的名字:"+Thread.currentThread().getName());
return new AsyncResult<AuthorDTO>(authorFacade.getAuthor(authorId));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
在service里調用異步執行的方法
/**
* 異步調用獲取文章信息
*
* @param articleId 文章ID
* @return 文章信息
*/
@Override
public Map getArticleDetailAsync(String articleId){
//同步調用獲取文章信息
ArticleDTO articleDTO = articleFacade.getArticle(articleId);
//異步調用獲取作者信息
Future authorFuture = asyncTask.getAuthor(articleDTO.getAuthorId());
Map<String,Object> map=new HashMap<>(10);
map.put("article",articleDTO);
try{
map.put("author",authorFuture.get());
}catch (Exception e){
log.error(e.getMessage());
}
return map;
}
PS:1、當一個類去調用標注了異步注解的方法時,當前類其實就是主線程,而調用標注異步注解的方法其實就相當于一個子線程,只有當主線程運行完了,其實可以用通過一些例子來證實的,給異步執行的方法阻塞幾秒鐘,查看下線程的執行情況
2、實際上,@Async還有一個參數,通過Bean名稱來指定調用的線程池-比如上例中設置的線程池參數不滿足業務需求,可以另外定義合適的線程池,調用時指明使用這個線程池-缺省時springboot會優先使用名稱為'taskExecutor'的線程池,如果沒有找到,才會使用其他類型為TaskExecutor或其子類的線程池。
進階
有時候我們不止希望異步執行任務,還希望任務執行完成后會有一個返回值,在java中提供了Future泛型接口,用來接收任務執行結果,springboot也提供了此類支持,使用實現了ListenableFuture接口的類如AsyncResult來作為返回值的載體。比如上例中,我們希望返回一個類型為String類型的值,可以將返回值改造為:
@Async
public ListenableFuture<String> sayHello(String name) {
String res = name + ":Hello World!";
LoggerFactory.getLogger(Hello.class).info(res);
return new AsyncResult<>(res);
}
調用返回值:
@Autowired
private Hello hello;
// 阻塞調用
hello.sayHello("yan").get();
// 限時調用
hello.sayHello("yan").get(1, TimeUnit.SECONDS)
總結
以上是生活随笔為你收集整理的springboot实现异步调用@Async的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql全外和交叉&&
- 下一篇: 腾讯游戏发布寒假暨春节假期期间未成年人游