电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

SpringBoot異步處理的四種實現(xiàn)方式

瀏覽:106日期:2023-03-10 15:53:40

本篇文章我們以SpringBoot中異步的使用(包括:異步調用和異步方法兩個維度)來進行講解。

異步請求與同步請求

我們先通過一張圖來區(qū)分一下異步請求和同步請求的區(qū)別:

SpringBoot異步處理的四種實現(xiàn)方式

在上圖中有三個角色:客戶端、Web容器和業(yè)務處理線程。

兩個流程中客戶端對Web容器的請求,都是同步的。因為它們在請求客戶端時都處于阻塞等待狀態(tài),并沒有進行異步處理。

在Web容器部分,第一個流程采用同步請求,第二個流程采用異步回調的形式。

通過異步處理,可以先釋放容器分配給請求的線程與相關資源,減輕系統(tǒng)負擔,從而增加了服務器對客戶端請求的吞吐量。但并發(fā)請求量較大時,通常會通過負載均衡的方案來解決,而不是異步。

Servlet3.0中的異步

Servlet 3.0之前,Servlet采用Thread-Per-Request的方式處理請求,即每一次Http請求都由一個線程從頭到尾處理。當涉及到耗時操作時,性能問題便比較明顯。

Servlet 3.0中提供了異步處理請求。可以先釋放容器分配給請求的線程與相關資源,減輕系統(tǒng)負擔,從而增加服務的吞吐量。

Servlet 3.0的異步是通過AsyncContext對象來完成的,它可以從當前線程傳給另一個線程,并歸還初始線程。新的線程處理完業(yè)務可以直接返回結果給客戶端。

AsyncContext對象可以從HttpServletRequest中獲取:

@RequestMapping('/async')public void async(HttpServletRequest request) { AsyncContext asyncContext = request.getAsyncContext();}

在AsyncContext中提供了獲取ServletRequest、ServletResponse和添加監(jiān)聽(addListener)等功能:

public interface AsyncContext { ServletRequest getRequest(); ServletResponse getResponse(); void addListener(AsyncListener var1); void setTimeout(long var1); // 省略其他方法}

不僅可以通過AsyncContext獲取Request和Response等信息,還可以設置異步處理超時時間。通常,超時時間(單位毫秒)是需要設置的,不然無限等下去不就與同步處理一樣了。

通過AsyncContext的addListener還可以添加監(jiān)聽事件,用來處理異步線程的開始、完成、異常、超時等事件回調。

addListener方法的參數(shù)AsyncListener的源碼如下:

public interface AsyncListener extends EventListener { // 異步執(zhí)行完畢時調用 void onComplete(AsyncEvent var1) throws IOException; // 異步線程執(zhí)行超時調用 void onTimeout(AsyncEvent var1) throws IOException; // 異步線程出錯時調用 void onError(AsyncEvent var1) throws IOException; // 異步線程開始時調用 void onStartAsync(AsyncEvent var1) throws IOException;}

通常,異常或超時時返回調用方錯誤信息,而異常時會處理一些清理和關閉操作或記錄異常日志等。

基于Servlet方式實現(xiàn)異步請求

下面直接看一個基于Servlet方式的異步請求示例:

@GetMapping(value = '/email/send')public void servletReq(HttpServletRequest request) { AsyncContext asyncContext = request.startAsync(); // 設置監(jiān)聽器:可設置其開始、完成、異常、超時等事件的回調處理 asyncContext.addListener(new AsyncListener() {@Overridepublic void onTimeout(AsyncEvent event) { System.out.println('處理超時了...');}@Overridepublic void onStartAsync(AsyncEvent event) { System.out.println('線程開始執(zhí)行');}@Overridepublic void onError(AsyncEvent event) { System.out.println('執(zhí)行過程中發(fā)生錯誤:' + event.getThrowable().getMessage());}@Overridepublic void onComplete(AsyncEvent event) { System.out.println('執(zhí)行完成,釋放資源');} }); //設置超時時間 asyncContext.setTimeout(6000); asyncContext.start(new Runnable() {@Overridepublic void run() { try {Thread.sleep(5000);System.out.println('內部線程:' + Thread.currentThread().getName());asyncContext.getResponse().getWriter().println('async processing'); } catch (Exception e) {System.out.println('異步處理發(fā)生異常:' + e.getMessage()); } // 異步請求完成通知,整個請求完成 asyncContext.complete();} }); //此時request的線程連接已經釋放了 System.out.println('主線程:' + Thread.currentThread().getName());}

啟動項目,訪問對應的URL,打印日志如下:

主線程:http-nio-8080-exec-4內部線程:http-nio-8080-exec-5執(zhí)行完成,釋放資源

可以看出,上述代碼先執(zhí)行完了主線程,也就是程序的最后一行代碼的日志打印,然后才是內部線程的執(zhí)行。內部線程執(zhí)行完成,AsyncContext的onComplete方法被調用。

如果通過瀏覽器訪問對應的URL,還可以看到該方法的返回值“async processing”。說明內部線程的結果同樣正常的返回到客戶端了。

基于Spring實現(xiàn)異步請求

基于Spring可以通過Callable、DeferredResult或者WebAsyncTask等方式實現(xiàn)異步請求。

基于Callable實現(xiàn)

對于一次請求(/email),基于Callable的處理流程如下:

1、Spring MVC開啟副線程處理業(yè)務(將Callable提交到TaskExecutor);

2、DispatcherServlet和所有的Filter退出Web容器的線程,但是response保持打開狀態(tài);

3、Callable返回結果,SpringMVC將原始請求重新派發(fā)給容器(再重新請求一次/email),恢復之前的處理;

4、DispatcherServlet重新被調用,將結果返回給用戶;

代碼實現(xiàn)示例如下:

@GetMapping('/email')public Callable<String> order() { System.out.println('主線程開始:' + Thread.currentThread().getName()); Callable<String> result = () -> {System.out.println('副線程開始:' + Thread.currentThread().getName());Thread.sleep(1000);System.out.println('副線程返回:' + Thread.currentThread().getName());return 'success'; }; System.out.println('主線程返回:' + Thread.currentThread().getName()); return result;}

訪問對應URL,控制臺輸入日志如下:

主線程開始:http-nio-8080-exec-1主線程返回:http-nio-8080-exec-1副線程開始:task-1副線程返回:task-1

通過日志可以看出,主線程已經完成了,副線程才進行執(zhí)行。同時,URL返回結果“success”。這也說明一個問題,服務器端的異步處理對客戶端來說是不可見的。

Callable默認使用SimpleAsyncTaskExecutor類來執(zhí)行,這個類非常簡單而且沒有重用線程。在實踐中,需要使用AsyncTaskExecutor類來對線程進行配置。

這里通過實現(xiàn)WebMvcConfigurer接口來完成線程池的配置。

@Configurationpublic class WebConfig implements WebMvcConfigurer { @Resource private ThreadPoolTaskExecutor myThreadPoolTaskExecutor; /** * 配置線程池 */ @Bean(name = 'asyncPoolTaskExecutor') public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(2);taskExecutor.setMaxPoolSize(10);taskExecutor.setQueueCapacity(25);taskExecutor.setKeepAliveSeconds(200);taskExecutor.setThreadNamePrefix('thread-pool-');// 線程池對拒絕任務(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認為后者taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());taskExecutor.initialize();return taskExecutor; } @Override public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {// 處理callable超時configurer.setDefaultTimeout(60 * 1000);configurer.setTaskExecutor(myThreadPoolTaskExecutor);configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor()); } @Bean public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {return new TimeoutCallableProcessingInterceptor(); }}

為了驗證打印的線程,我們將實例代碼中的System.out.println替換成日志輸出,會發(fā)現(xiàn)在使用線程池之前,打印日志如下:

2021-02-21 09:45:37.144 INFO 8312 --- [nio-8080-exec-1] c.s.learn.controller.AsynController : 主線程開始:http-nio-8080-exec-12021-02-21 09:45:37.144 INFO 8312 --- [nio-8080-exec-1] c.s.learn.controller.AsynController : 主線程返回:http-nio-8080-exec-12021-02-21 09:45:37.148 INFO 8312 --- [ task-1] c.s.learn.controller.AsynController : 副線程開始:task-12021-02-21 09:45:38.153 INFO 8312 --- [ task-1] c.s.learn.controller.AsynController : 副線程返回:task-1

線程名稱為“task-1”。讓線程池生效之后,打印日志如下:

2021-02-21 09:50:28.950 INFO 8339 --- [nio-8080-exec-1] c.s.learn.controller.AsynController : 主線程開始:http-nio-8080-exec-12021-02-21 09:50:28.951 INFO 8339 --- [nio-8080-exec-1] c.s.learn.controller.AsynController : 主線程返回:http-nio-8080-exec-12021-02-21 09:50:28.955 INFO 8339 --- [ thread-pool-1] c.s.learn.controller.AsynController : 副線程開始:thread-pool-12021-02-21 09:50:29.956 INFO 8339 --- [ thread-pool-1] c.s.learn.controller.AsynController : 副線程返回:thread-pool-1

線程名稱為“thread-pool-1”,其中前面的“thread-pool”正是我們配置的線程池前綴。

除了線程池的配置,還可以配置統(tǒng)一異常處理,這里就不再演示了。

基于WebAsyncTask實現(xiàn)

Spring提供的WebAsyncTask是對Callable的包裝,提供了更強大的功能,比如:處理超時回調、錯誤回調、完成回調等。

@GetMapping('/webAsyncTask')public WebAsyncTask<String> webAsyncTask() { log.info('外部線程:' + Thread.currentThread().getName()); WebAsyncTask<String> result = new WebAsyncTask<>(60 * 1000L, new Callable<String>() {@Overridepublic String call() { log.info('內部線程:' + Thread.currentThread().getName()); return 'success';} }); result.onTimeout(new Callable<String>() {@Overridepublic String call() { log.info('timeout callback'); return 'timeout callback';} }); result.onCompletion(new Runnable() {@Overridepublic void run() { log.info('finish callback');} }); return result;}

訪問對應請求,打印日志:

2021-02-21 10:22:33.028 INFO 8547 --- [nio-8080-exec-1] c.s.learn.controller.AsynController : 外部線程:http-nio-8080-exec-12021-02-21 10:22:33.033 INFO 8547 --- [ thread-pool-1] c.s.learn.controller.AsynController : 內部線程:thread-pool-12021-02-21 10:22:33.055 INFO 8547 --- [nio-8080-exec-2] c.s.learn.controller.AsynController : finish callback

基于DeferredResult實現(xiàn)

DeferredResult使用方式與Callable類似,但在返回結果時不一樣,它返回的時實際結果可能沒有生成,實際的結果可能會在另外的線程里面設置到DeferredResult中去。

DeferredResult的這個特性對實現(xiàn)服務端推技術、訂單過期時間處理、長輪詢、模擬MQ的功能等高級應用非常重要。

關于DeferredResult的使用先來看一下官方的例子和說明:

@RequestMapping('/quotes')@ResponseBodypublic DeferredResult<String> quotes() { DeferredResult<String> deferredResult = new DeferredResult<String>(); // Save the deferredResult in in-memory queue ... return deferredResult;}// In some other thread...deferredResult.setResult(data);

上述示例中我們可以發(fā)現(xiàn)DeferredResult的調用并不一定在Spring MVC當中,它可以是別的線程。官方的解釋也是如此:

In this case the return value will also be produced from a separate thread. However, that thread is not known to Spring MVC. For example the result may be produced in response to some external event such as a JMS message, a scheduled task, etc.

也就是說,DeferredResult返回的結果也可能是由MQ、定時任務或其他線程觸發(fā)。來個實例:

@Controller@RequestMapping('/async/controller')public class AsyncHelloController { private List<DeferredResult<String>> deferredResultList = new ArrayList<>(); @ResponseBody @GetMapping('/hello') public DeferredResult<String> helloGet() throws Exception {DeferredResult<String> deferredResult = new DeferredResult<>();//先存起來,等待觸發(fā)deferredResultList.add(deferredResult);return deferredResult; } @ResponseBody @GetMapping('/setHelloToAll') public void helloSet() throws Exception {// 讓所有hold住的請求給與響應deferredResultList.forEach(d -> d.setResult('say hello to all')); }}

第一個請求/hello,會先將deferredResult存起來,前端頁面是一直等待(轉圈)狀態(tài)。直到發(fā)第二個請求:setHelloToAll,所有的相關頁面才會有響應。

整個執(zhí)行流程如下:

controller返回一個DeferredResult,把它保存到內存里或者List里面(供后續(xù)訪問); Spring MVC調用request.startAsync(),開啟異步處理;與此同時將DispatcherServlet里的攔截器、Filter等等都馬上退出主線程,但是response仍然保持打開的狀態(tài); 應用通過另外一個線程(可能是MQ消息、定時任務等)給DeferredResult#setResult值。然后SpringMVC會把這個請求再次派發(fā)給servlet容器; DispatcherServlet再次被調用,然后處理后續(xù)的標準流程;

通過上述流程可以發(fā)現(xiàn):利用DeferredResult可實現(xiàn)一些長連接的功能,比如當某個操作是異步時,可以先保存對應的DeferredResult對象,當異步通知回來時,再找到這個DeferredResult對象,在setResult處理結果即可。從而提高性能。

SpringBoot中的異步實現(xiàn)

在SpringBoot中將一個方法聲明為異步方法非常簡單,只需兩個注解即可@EnableAsync和@Async。其中@EnableAsync用于開啟SpringBoot支持異步的功能,用在SpringBoot的啟動類上。@Async用于方法上,標記該方法為異步處理方法。

需要注意的是@Async并不支持用于被@Configuration注解的類的方法上。同一個類中,一個方法調用另外一個有@Async的方法,注解也是不會生效的。

@EnableAsync的使用示例:

@SpringBootApplication@EnableAsyncpublic class App { public static void main(String[] args) {SpringApplication.run(App.class, args); }}

@Async的使用示例:

@Servicepublic class SyncService { @Async public void asyncEvent() {// 業(yè)務處理 }}

@Async注解的使用與Callable有類似之處,在默認情況下使用的都是SimpleAsyncTaskExecutor線程池,可參考Callable中的方式來自定義線程池。

下面通過一個實例來驗證一下,啟動類上使用@EnableAsync,然后定義Controller類:

@RestControllerpublic class IndexController { @Resource private UserService userService; @RequestMapping('/async') public String async(){System.out.println('--IndexController--1');userService.sendSms();System.out.println('--IndexController--4');return 'success'; }}

定義Service及異步方法:

@Servicepublic class UserService { @Async public void sendSms(){System.out.println('--sendSms--2');IntStream.range(0, 5).forEach(d -> { try {Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace(); }});System.out.println('--sendSms--3'); }}

如果先注釋掉@EnableAsync和@Async注解,即正常情況下的業(yè)務請求,打印日志為:

--IndexController--1--sendSms--2--sendSms--3--IndexController--4

使用@EnableAsync和@Async注解時,打印日志如下:

--IndexController--1--IndexController--4--sendSms--2--sendSms--3

通過日志的對比我們可以看出,使用了@Async的方法,會被當成一個子線程。所以,整個sendSms方法會在主線程執(zhí)行完了之后執(zhí)行。

這樣的效果是不是跟我們上面使用的其他形式的異步異曲同工?所以在文章最開始已經說到,網絡上所謂的“異步調用與異步請求的區(qū)別”是并不存儲在的,本質上都是一回事,只不過實現(xiàn)形式不同而已。這里所提到異步方法,也就是將方法進行異步處理而已。

@Async、WebAsyncTask、Callable、DeferredResult的區(qū)別

所在的包不同:

@Async:org.springframework.scheduling.annotation; WebAsyncTask:org.springframework.web.context.request.async; Callable:java.util.concurrent; DeferredResult:org.springframework.web.context.request.async;

通過所在的包,我們應該隱隱約約感到一些區(qū)別,比如@Async是位于scheduling包中,而WebAsyncTask和DeferredResult是用于Web(Spring MVC)的,而Callable是用于concurrent(并發(fā))處理的。

對于Callable,通常用于Controller方法的異步請求,當然也可以用于替換Runable的方式。在方法的返回上與正常的方法有所區(qū)別:

// 普通方法public String aMethod(){}// 對照Callable方法public Callable<String> aMethod(){}

而WebAsyncTask是對Callable的封裝,提供了一些事件回調的處理,本質上區(qū)別不大。

DeferredResult使用方式與Callable類似,重點在于跨線程之間的通信。

@Async也是替換Runable的一種方式,可以代替我們自己創(chuàng)建線程。而且適用的范圍更廣,并不局限于Controller層,而可以是任何層的方法上。

當然,大家也可以從返回結果,異常處理等角度來分析一下,這里就不再展開了。

小結

經過上述的一步步分析,大家應該對于Servlet3.0及Spring中對異步處理有所了解。當了解了這些基礎理論,實戰(zhàn)實例,使用方法及注意事項之后,想必更能夠對網絡上的相關知識能夠進一步的去偽存真。

到此這篇關于SpringBoot異步處理的四種實現(xiàn)方式的文章就介紹到這了,更多相關SpringBoot異步內容請搜索好吧啦網以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Spring
相關文章:
主站蜘蛛池模板: 铁素体测量仪/检测仪/铁素体含量测试仪-苏州圣光仪器有限公司 | 螺旋叶片_螺旋叶片成型机_绞龙叶片_莱州源泽机械制造有限公司 | 一点车讯-汽车网站,每天一点最新车讯! | 商标转让-商标注册-商标查询-软著专利服务平台 - 赣江万网 | 泰安办公家具-泰安派格办公用品有限公司 | LINK FASHION 童装·青少年装展 河南卓美创业科技有限公司-河南卓美防雷公司-防雷接地-防雷工程-重庆避雷针-避雷器-防雷检测-避雷带-避雷针-避雷塔、机房防雷、古建筑防雷等-山西防雷公司 | 啤酒设备-小型啤酒设备-啤酒厂设备-济南中酿机械设备有限公司 | 自动部分收集器,进口无油隔膜真空泵,SPME固相微萃取头-上海楚定分析仪器有限公司 | 欧必特空气能-商用空气能热水工程,空气能热水器,超低温空气源热泵生产厂家-湖南欧必特空气能公司 | 广州活动策划公司-15+年专业大型公关活动策划执行管理经验-睿阳广告 | 便携式XPDM露点仪-在线式防爆露点仪-增强型烟气分析仪-约克仪器 冰雕-冰雪世界-大型冰雕展制作公司-赛北冰雕官网 | 贝朗斯动力商城(BRCPOWER.COM) - 买叉车蓄电池上贝朗斯商城,价格更超值,品质有保障! | 河南橡胶接头厂家,河南波纹补偿器厂家,河南可曲挠橡胶软连接,河南套筒补偿器厂家-河南正大阀门 | 进口便携式天平,外校_十万分之一分析天平,奥豪斯工业台秤,V2000防水秤-重庆珂偌德科技有限公司(www.crdkj.com) | 淄博不锈钢无缝管,淄博不锈钢管-鑫门物资有限公司 | 镀锌钢格栅_热镀锌格栅板_钢格栅板_热镀锌钢格板-安平县昊泽丝网制品有限公司 | 船老大板材_浙江船老大全屋定制_船老大官网 | 沟盖板_复合沟盖板厂_电力盖板_树脂雨水篦子-淄博拜斯特 | 杭州双螺杆挤出机-百科 | 布袋除尘器-单机除尘器-脉冲除尘器-泊头市兴天环保设备有限公司 布袋除尘器|除尘器设备|除尘布袋|除尘设备_诺和环保设备 | 碳刷_刷握_集电环_恒压簧_电刷厂家-上海丹臻机电科技有限公司 | 杭州中央空调维修_冷却塔/新风机柜/热水器/锅炉除垢清洗_除垢剂_风机盘管_冷凝器清洗-杭州亿诺能源有限公司 | 东莞市海宝机械有限公司-不锈钢分选机-硅胶橡胶-生活垃圾-涡电流-静电-金属-矿石分选机 | 迪威娱乐|迪威娱乐客服|18183620002 | 车牌识别道闸_停车场收费系统_人脸识别考勤机_速通门闸机_充电桩厂家_中全清茂官网 | 短信营销平台_短信群发平台_106短信发送平台-河南路尚 | 自动焊锡机_点胶机_螺丝机-锐驰机器人 | 制氮设备-变压吸附制氮设备-制氧设备-杭州聚贤气体设备制造有限公司 | 杭州标识标牌|文化墙|展厅|导视|户内外广告|发光字|灯箱|铭阳制作公司 - 杭州标识标牌|文化墙|展厅|导视|户内外广告|发光字|灯箱|铭阳制作公司 | 解放卡车|出口|济南重汽|报价大全|山东三维商贸有限公司 | 搬运设备、起重设备、吊装设备—『龙海起重成套设备』 | 车件|铜件|车削件|车床加工|五金冲压件-PIN针,精密车件定制专业厂商【东莞品晔】 | 走心机厂家,数控走心机-台州博城智能科技有限公司 | 济南玻璃安装_济南玻璃门_济南感应门_济南玻璃隔断_济南玻璃门维修_济南镜片安装_济南肯德基门_济南高隔间-济南凯轩鹏宇玻璃有限公司 | 微学堂-电动能源汽车评测_电动车性能分享网 | TPE_TPE热塑性弹性体_TPE原料价格_TPE材料厂家-惠州市中塑王塑胶制品公司- 中塑王塑胶制品有限公司 | 合肥汽车充电桩_安徽充电桩_电动交流充电桩厂家_安徽科帝新能源科技有限公司 | 防水套管_柔性防水套管_刚性防水套管-巩义市润达管道设备制造有限公司 | 送料机_高速冲床送料机_NC伺服滚轮送料机厂家-东莞市久谐自动化设备有限公司 | 办公室装修_上海办公室设计装修_时尚办公新主张-后街印象 | 厂厂乐-汇聚海量采购信息的B2B微营销平台-厂厂乐官网 |