一、課程目標(biāo)
熟悉spring的異步框架找蜜,學(xué)會使用異步@Async注解
二饼暑、為什么要用異步框架,它解決什么問題洗做?
在SpringBoot的日常開發(fā)中弓叛,一般都是同步調(diào)用的。但經(jīng)常有特殊業(yè)務(wù)需要做異步來處理诚纸,例如:注冊新用戶撰筷,送100個積分,或下單成功畦徘,發(fā)送push消息等等毕籽。
就拿注冊新用戶為什么要異步處理?
- 第一個原因:容錯性井辆,如果送積分出現(xiàn)異常关筒,不能因為送積分而導(dǎo)致用戶注冊失敗杯缺;
因為用戶注冊是主要功能蒸播,送積分是次要功能,即使送積分異常也要提示用戶注冊成功萍肆,然后后面在針對積分異常做補償處理袍榆。 - 第二個原因:提升性能,例如注冊用戶花了20毫秒塘揣,送積分花費50毫秒包雀,如果用同步的話,總耗時70毫秒亲铡,用異步的話才写,無需等待積分,故耗時20毫秒奖蔓。
故琅摩,異步能解決2個問題,性能和容錯性锭硼。
三、SpringBoot異步調(diào)用
在SpringBoot中使用異步調(diào)用是很簡單的蜕劝,只需要使用@Async注解即可實現(xiàn)方法的異步調(diào)用檀头。
四轰异、@Async異步調(diào)用例子
步驟1:開啟異步任務(wù)
采用@EnableAsync來啟用異步任務(wù)支持,另外需要用@Configuration來把當(dāng)前類加入springIOC容器中
@Configuration
@EnableAsync
public class SyncConfiguration {
}
步驟2:在方法標(biāo)記為異步調(diào)用
增加一個ScoreService類,用于積分處理暑始。
@Async添加在方法上搭独,代表該方法為異步處理。
@Service
public class ScoreService {
private static final Logger logger = LoggerFactory.getLogger(ScoreService.class);
@Async
public void addScore(){
//TODO 模擬睡10秒廊镜,用于贈送積分處理
try {
Thread.sleep(1000*10);
logger.info("--------------處理積分--------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
添加一個測試類牙肝,TestController
@RestController
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private ScoreService scoreService;
@RequestMapping("/sync")
public String createUser() {
logger.info("--------------注冊用戶--------------------");
this.scoreService.addScore();
return "OK";
}
}
步驟3:體驗效果
在瀏覽器輸入:http://127.0.0.1:9090/sync
從日志的截圖可以看出2點:
- 異步日志相隔10s
- 不同的線程,線程[nio-9090-exec-1]代表的是tomcat的線程嗤朴,線程[task-1]代表的是異步Async的線程
五配椭、為什么要給Async自定義線程池?
@Async注解雹姊,在默認(rèn)情況下用的是SimpleAsyncTaskExecutor線程池股缸,該線程池不是真正意義上的線程池,因為線程不重用吱雏,每次調(diào)用都會新建一條線程敦姻。
可以通過控制臺日志輸出查看,每次打印的線程名都是[task-1]歧杏、[task-2]镰惦、[task-3]、[task-4].....遞增的犬绒。
@Async注解異步框架提供多種線程
SimpleAsyncTaskExecutor:不是真的線程池旺入,這個類不重用線程,每次調(diào)用都會創(chuàng)建一個新的線程懂更。
SyncTaskExecutor:這個類沒有實現(xiàn)異步調(diào)用眨业,只是一個同步操作。只適用于不需要多線程的地方
ConcurrentTaskExecutor:Executor的適配類沮协,不推薦使用龄捡。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類
ThreadPoolTaskScheduler:可以使用cron表達(dá)式
ThreadPoolTaskExecutor :最常使用慷暂,推薦聘殖。 其實質(zhì)是對java.util.concurrent.ThreadPoolExecutor的包裝
六、為@Async實現(xiàn)一個自定義線程池
步驟1:配置線程池
@Configuration
@EnableAsync
public class SyncConfiguration {
@Bean(name = "scorePoolTaskExecutor")
public ThreadPoolTaskExecutor getScorePoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心線程數(shù)
taskExecutor.setCorePoolSize(10);
//線程池維護(hù)線程的最大數(shù)量,只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程
taskExecutor.setMaxPoolSize(100);
//緩存隊列
taskExecutor.setQueueCapacity(50);
//許的空閑時間,當(dāng)超過了核心線程出之外的線程在空閑時間到達(dá)之后會被銷毀
taskExecutor.setKeepAliveSeconds(200);
//異步方法內(nèi)部線程名稱
taskExecutor.setThreadNamePrefix("score-");
/**
* 當(dāng)線程池的任務(wù)緩存隊列已滿并且線程池中的線程數(shù)目達(dá)到maximumPoolSize行瑞,如果還有任務(wù)到來就會采取任務(wù)拒絕策略
* 通常有以下四種策略:
* ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常奸腺。
* ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常血久。
* ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務(wù)突照,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
* ThreadPoolExecutor.CallerRunsPolicy:重試添加當(dāng)前的任務(wù),自動重復(fù)調(diào)用 execute() 方法氧吐,直到成功
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
步驟2:為@Async指定線程池
@Async("scorePoolTaskExecutor")
public void addScore2(){
//TODO 模擬睡5秒讹蘑,用于贈送積分處理
try {
Thread.sleep(1000*5);
logger.info("--------------處理積分--------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
以上代碼挺簡單的末盔,就是給@Async("scorePoolTaskExecutor")加個參數(shù)即可。
@RequestMapping("/sync2")
public String createUser2() {
logger.info("--------------注冊用戶2--------------------");
this.scoreService.addScore2();
return "OK";
}
步驟3:體驗效果
在瀏覽器輸入:http://127.0.0.1:9090/sync2
2019-09-17 16:25:06.128 INFO 47776 --- [nio-9090-exec-1] com.agan.boot.controller.TestController : --------------注冊用戶2--------------------
2019-09-17 16:25:11.136 INFO 47776 --- [ score-1] com.agan.boot.service.ScoreService : --------------處理積分--------------------
從日志的可以看出:
線程[score-1]代表的是我們自定義的線程池score線程
七座慰、課后練習(xí)
在現(xiàn)實的互聯(lián)網(wǎng)項目開發(fā)中陨舱,針對高并發(fā)的請求,一般的做法是高并發(fā)接口單獨線程池隔離處理版仔。
假設(shè)現(xiàn)在2個高并發(fā)接口:
一個是修改用戶信息接口游盲,刷新用戶redis緩存.
一個是下訂單接口,發(fā)送app push信息.
請參考本課程內(nèi)容蛮粮,設(shè)計2個線程池益缎,分別用于[刷新用戶redis緩存]和[發(fā)送app push信息]
-----------------------------配套學(xué)習(xí)資料-----------------------------
- 課后練習(xí)作業(yè)請?zhí)峤坏絈Q群(1號QQ群3000人已滿,請加2號群:985378659[群名:SpringBoot架構(gòu)師])
- 本課程配套免費視頻教程
https://study.163.com/course/introduction/1004576013.htm?share=1&shareId=1016481220 - 本課程配套源碼地址:https://github.com/agan-java/agan-boot