前言
來(lái)啦老鐵掌眠!
筆者學(xué)習(xí)Spring Boot有一段時(shí)間了,附上Spring Boot系列學(xué)習(xí)文章幕屹,歡迎取閱蓝丙、賜教:
- 5分鐘入手Spring Boot;
- Spring Boot數(shù)據(jù)庫(kù)交互之Spring Data JPA;
- Spring Boot數(shù)據(jù)庫(kù)交互之Mybatis;
- Spring Boot視圖技術(shù);
- Spring Boot之整合Swagger;
- Spring Boot之junit單元測(cè)試踩坑;
- 如何在Spring Boot中使用TestNG;
- Spring Boot之整合logback日志;
- Spring Boot之整合Spring Batch:批處理與任務(wù)調(diào)度;
- Spring Boot之整合Spring Security: 訪(fǎng)問(wèn)認(rèn)證;
- Spring Boot之整合Spring Security: 授權(quán)管理;
- Spring Boot之多數(shù)據(jù)庫(kù)源:極簡(jiǎn)方案;
- Spring Boot之使用MongoDB數(shù)據(jù)庫(kù)源;
近期項(xiàng)目忙碌,家里事情也接踵而至香嗓,今天咱簡(jiǎn)單學(xué)點(diǎn)Spring Boot知識(shí):
-
Spring Boot之多線(xiàn)程迅腔、異步:@Async
通常情況下,我們基于Spring Boot寫(xiě)的API或方法靠娱,都是同步類(lèi)型的沧烈,同步過(guò)程是阻塞式的,前一行代碼在未得到結(jié)果之前像云,會(huì)產(chǎn)生阻塞锌雀,后續(xù)的代碼就只能等待。比如迅诬,調(diào)用一個(gè)API腋逆,該API與數(shù)據(jù)庫(kù)交互,然后返回API結(jié)果侈贷,數(shù)據(jù)庫(kù)交互如果用了2秒鐘惩歉,那么返回API結(jié)果這個(gè)過(guò)程就要等2秒鐘,才能發(fā)生俏蛮;
而實(shí)際場(chǎng)景中撑蚌,有些功能其實(shí)不需要等待結(jié)果就可以執(zhí)行后續(xù)代碼,比如:
-
發(fā)郵件功能搏屑,這個(gè)功能往往不用關(guān)心整個(gè)過(guò)程花了多久時(shí)間争涌,只要最終對(duì)方能夠收到郵件即可,因此完全不用等待發(fā)郵件服務(wù)返回辣恋,可采用異步方式亮垫;
-
一些任務(wù),如批處理任務(wù)等伟骨,觸發(fā)時(shí)往往只需要得到服務(wù)器的應(yīng)答饮潦,而不用等到任務(wù)執(zhí)行結(jié)束才告訴客戶(hù)端,也可采用異步的方式底靠;
-
等害晦;
通常,我們采用多線(xiàn)程技術(shù)來(lái)實(shí)現(xiàn)異步過(guò)程,而Spring Boot中壹瘟,對(duì)這個(gè)過(guò)程又做了簡(jiǎn)化鲫剿,使用起來(lái)非常簡(jiǎn)單,接下來(lái)我們就一起來(lái)探索一下稻轨!
項(xiàng)目代碼已上傳Git Hub倉(cāng)庫(kù)灵莲,歡迎取閱:
整體步驟
- 快速建立Spring Boot項(xiàng)目;
- 修飾項(xiàng)目啟動(dòng)類(lèi)殴俱;
- 編寫(xiě)Service政冻;
- 編寫(xiě)Controller;
- 驗(yàn)證效果线欲;
- 線(xiàn)程池管理配置類(lèi)明场;
1. 快速建立Spring Boot項(xiàng)目;
請(qǐng)參考5分鐘入手Spring Boot;
2. 修飾項(xiàng)目啟動(dòng)類(lèi)李丰;
在項(xiàng)目啟動(dòng)類(lèi)上添加注解@EnableAsync即可:
package com.github.dylanz666;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@SpringBootApplication
@EnableAsync
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
3. 編寫(xiě)Service苦锨;
為了演示同步與異步的差異,以及異步的不同用法趴泌,我會(huì)編寫(xiě)3個(gè)service類(lèi)和3個(gè)controller類(lèi)舟舒;
1). 同步service類(lèi);
package com.github.dylanz666.service;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@Service
public class SyncTaskService {
public void syncTask1() throws InterruptedException {
Thread.sleep(2000);//模擬阻塞操作
System.out.println(new Date() + ": syncTask1 complete.");
}
public void syncTask2() throws InterruptedException {
Thread.sleep(2000);//模擬阻塞操作
System.out.println(new Date() + ": syncTask2 complete.");
}
public void syncTask3() throws InterruptedException {
Thread.sleep(2000);//模擬阻塞操作
System.out.println(new Date() + ": syncTask3 complete.");
}
}
2). 簡(jiǎn)單的異步service類(lèi)嗜憔;
package com.github.dylanz666.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@Service
public class AsyncTaskService {
@Async
public void asyncTask1() throws InterruptedException {
Thread.sleep(2000);//模擬阻塞操作
System.out.println(new Date() + ": asyncTask1 complete.");
}
@Async
public void asyncTask2() throws InterruptedException {
Thread.sleep(2000);//模擬阻塞操作
System.out.println(new Date() + ": asyncTask2 complete.");
}
@Async
public void asyncTask3() throws InterruptedException {
Thread.sleep(2000);//模擬阻塞操作
System.out.println(new Date() + ": asyncTask3 complete.");
}
}
3). 異步拓展應(yīng)用的service類(lèi)秃励;
package com.github.dylanz666.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.Future;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@Service
@Async
public class AsyncTaskService2 {
public Future<String> asyncTask1() throws InterruptedException {
Thread.sleep(10000);//模擬阻塞操作
System.out.println(new Date() + ": asyncTask1 complete");
return new AsyncResult<String>("asyncTask1 complete");
}
public Future<String> asyncTask2() throws InterruptedException {
Thread.sleep(10000);//模擬阻塞操作
System.out.println(new Date() + ": asyncTask1 complete");
return new AsyncResult<String>("asyncTask1 complete");
}
public Future<String> asyncTask3() throws InterruptedException {
Thread.sleep(10000);//模擬阻塞操作
System.out.println(new Date() + ": asyncTask1 complete");
return new AsyncResult<String>("asyncTask1 complete");
}
}
簡(jiǎn)單解讀一下:
- 要使一個(gè)方法或類(lèi)稱(chēng)為異步方法或類(lèi),只需要在方法或類(lèi)上添加@Async即可吉捶,非常簡(jiǎn)單夺鲜!
- 可以通過(guò)方法返回Future類(lèi)型(也可以用ListenableFuture)的對(duì)象,用于操作異步方法呐舔、提供異步方法的執(zhí)行狀態(tài)等谣旁;
4. 編寫(xiě)Controller;
1). 用于演示同步過(guò)程的API;
package com.github.dylanz666.controller;
import com.github.dylanz666.service.AsyncTaskService;
import com.github.dylanz666.service.SyncTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@RestController
public class SyncTaskController {
@Autowired
private SyncTaskService syncTaskService;
@GetMapping("/sync/task")
@ResponseBody
public String execute() throws InterruptedException {
long startTimeStamp = System.currentTimeMillis();
syncTaskService.syncTask1();
syncTaskService.syncTask2();
syncTaskService.syncTask3();
long endTimeStamp = System.currentTimeMillis();
String message = "sync tasks are complete, duration: " + (endTimeStamp - startTimeStamp) + " ms";
System.out.println(message);
return message;
}
}
2). 用于演示簡(jiǎn)單異步過(guò)程的API;
package com.github.dylanz666.controller;
import com.github.dylanz666.service.AsyncTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@RestController
public class AsyncTaskController {
@Autowired
private AsyncTaskService asyncTaskService;
@GetMapping("/async/task")
@ResponseBody
public String execute() throws InterruptedException {
long startTimeStamp = System.currentTimeMillis();
asyncTaskService.asyncTask1();
asyncTaskService.asyncTask2();
asyncTaskService.asyncTask3();
long endTimeStamp = System.currentTimeMillis();
String message = "async tasks are triggered successfully, duration: " + (endTimeStamp - startTimeStamp) + " ms";
System.out.println(message);
return message;
}
}
3). 用于演示異步拓展應(yīng)用的API;
package com.github.dylanz666.controller;
import com.github.dylanz666.service.AsyncTaskService2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.Future;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@RestController
@RequestMapping("/async/complex")
public class AsyncTaskController2 {
@Autowired
private AsyncTaskService2 asyncTaskService2;
public static String status = "async tasks are not triggered.";
public static Future<String> task1;
public static Future<String> task2;
public static Future<String> task3;
@GetMapping("/task")
@ResponseBody
public String execute() throws InterruptedException {
long startTimeStamp = System.currentTimeMillis();
task1 = asyncTaskService2.asyncTask1();
task2 = asyncTaskService2.asyncTask2();
task3 = asyncTaskService2.asyncTask3();
long endTimeStamp = System.currentTimeMillis();
status = "async tasks are doing.";
String message = "async tasks are triggered successfully, duration: " + (endTimeStamp - startTimeStamp) + " ms";
System.out.println(message);
return message;
}
@GetMapping("/task/status")
@ResponseBody
public String getTasksStatus() {
assert task1 != null;
if (task1.isDone() && task2.isDone() && task3.isDone()) {
status = "async tasks are done.";
}
return status;
}
@GetMapping("/task/status/{taskId}")
@ResponseBody
public Boolean getTaskStatus(@PathVariable(name = "taskId") int taskId) {
boolean taskStatus = false;
switch (taskId) {
case 1:
taskStatus = task1.isDone();
break;
case 2:
taskStatus = task2.isDone();
break;
case 3:
taskStatus = task3.isDone();
break;
}
return taskStatus;
}
}
項(xiàng)目整體結(jié)構(gòu)如下:
5. 驗(yàn)證效果滋早;
啟動(dòng)項(xiàng)目:
1). 驗(yàn)證同步API和執(zhí)行結(jié)果;
瀏覽器直接訪(fǎng)問(wèn) http://127.0.0.1:8080/sync/task:
解讀:
- 由于syncTask1砌们、syncTask2杆麸、syncTask3各等待了2秒鐘,最終整體用時(shí)6.002秒才返回API結(jié)果浪感;
- syncTask1昔头、syncTask2、syncTask3按代碼中指定的順序影兽,順序執(zhí)行揭斧;
2). 驗(yàn)證簡(jiǎn)單異步API和執(zhí)行結(jié)果;
瀏覽器直接訪(fǎng)問(wèn) http://127.0.0.1:8080/async/task:
解讀:
- 雖然asyncTask1、asyncTask2讹开、asyncTask3的代碼中也寫(xiě)了等待2秒的代碼盅视,但API調(diào)用時(shí)卻未等待,而是直接返回結(jié)果旦万,僅用了4毫秒闹击,速度非常快成艘;
- asyncTask1赏半、asyncTask2、asyncTask3三個(gè)方法均采用@Async注解淆两,三者并行執(zhí)行断箫,即使用了3個(gè)線(xiàn)程,基本無(wú)先后概念秋冰;
3). 驗(yàn)證異步拓展應(yīng)用API和執(zhí)行結(jié)果仲义;
這里頭的API主要演示@Async可以寫(xiě)在類(lèi)上,可以通過(guò)返回Future類(lèi)型的對(duì)象丹莲,對(duì)異步任務(wù)進(jìn)行處理和獲取其信息光坝;
瀏覽器直接訪(fǎng)問(wèn) http://127.0.0.1:8080/async/complex/task:
-
瀏覽器直接訪(fǎng)問(wèn) http://127.0.0.1:8080/async/complex/task/status 和http://127.0.0.1:8080/async/complex/task/status/1;
-
由于asyncTask1甥材、asyncTask2盯另、asyncTask3均設(shè)置了10秒中的阻塞時(shí)間,因此前10秒鐘洲赵,整體task的狀態(tài)和各個(gè)task的狀態(tài)應(yīng)該是未完成的:
-
10秒鐘過(guò)去后鸳惯,瀏覽器再次訪(fǎng)問(wèn) http://127.0.0.1:8080/async/complex/task/status 和http://127.0.0.1:8080/async/complex/task/status/1;
-
過(guò)了設(shè)置好的10秒阻塞時(shí)間叠萍,整體task的狀態(tài)和各個(gè)task的狀態(tài)應(yīng)該是完成的:
這樣的拓展芝发,我們不僅會(huì)使用多線(xiàn)程、異步苛谷,而且能夠獲取異步方法的狀態(tài)辅鲸,真香!
6. 線(xiàn)程池管理配置類(lèi)腹殿;
上述這種方式独悴,當(dāng)并發(fā)量很小時(shí),上述方式一般不會(huì)有問(wèn)題锣尉,但當(dāng)并發(fā)量很大時(shí)刻炒,可能會(huì)遇到一些問(wèn)題:
- 由于我們沒(méi)有對(duì)線(xiàn)程數(shù)量進(jìn)行限制,如果所有請(qǐng)求都去占用資源自沧,很容易使資源負(fù)載過(guò)大坟奥,甚至宕機(jī);
- 當(dāng)我們需要的并發(fā)執(zhí)行線(xiàn)程數(shù)量很多時(shí),且每個(gè)線(xiàn)程執(zhí)行很短的時(shí)間就結(jié)束了爱谁,這樣晒喷,我們頻繁的創(chuàng)建、銷(xiāo)毀線(xiàn)程就大大降低了工作效率(創(chuàng)建和銷(xiāo)毀線(xiàn)程需要時(shí)間管行、資源)厨埋;
- 線(xiàn)程池可以使一個(gè)線(xiàn)程執(zhí)行完任務(wù)之后,繼續(xù)去執(zhí)行下一個(gè)任務(wù)捐顷,不被銷(xiāo)毀荡陷,這樣線(xiàn)程利用率就提高了。
因此,有必要使用線(xiàn)程池對(duì)線(xiàn)程進(jìn)行管理。
通常我們使用Spring提供的ThreadPoolTaskExecutor弟劲,進(jìn)行線(xiàn)程管理;
項(xiàng)目?jī)?nèi)創(chuàng)建config包唉地,并建立配置類(lèi)ThreadPoolConfig(名字隨意),代碼如下:
package com.github.dylanz666.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author : dylanz
* @since : 09/27/2020
*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//設(shè)置核心線(xiàn)程數(shù)
executor.setCorePoolSize(5);
//設(shè)置最大線(xiàn)程數(shù)
executor.setMaxPoolSize(10);
//設(shè)置隊(duì)列容量
executor.setQueueCapacity(20);
//設(shè)置線(xiàn)程活躍時(shí)間(秒)
executor.setKeepAliveSeconds(60);
//設(shè)置默認(rèn)線(xiàn)程名稱(chēng)
executor.setThreadNamePrefix("demo-");
//設(shè)置拒絕策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//等待所有任務(wù)結(jié)束后再關(guān)閉線(xiàn)程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
如果采用配置類(lèi)方式管理線(xiàn)程传透,則項(xiàng)目入口類(lèi)的@EnableAsync可以去除耘沼;
此時(shí)項(xiàng)目整體結(jié)構(gòu):
至此,我們學(xué)會(huì)了Spring Boot多線(xiàn)程朱盐、異步的基本使用方法群嗤,非常簡(jiǎn)單,相信未來(lái)定能派上用場(chǎng)1铡?衩亍!
如果本文對(duì)您有幫助躯肌,麻煩點(diǎn)贊+關(guān)注者春!
謝謝!