中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

SpringBoot | 第二十一章:異步開發(fā)之異步調(diào)用

2018-09-17    來源:importnew

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

前言

上一章節(jié),我們知道了如何進(jìn)行異步請求的處理。除了異步請求,一般上我們用的比較多的應(yīng)該是異步調(diào)用。通常在開發(fā)過程中,會(huì)遇到一個(gè)方法是和實(shí)際業(yè)務(wù)無關(guān)的,沒有緊密性的。比如記錄日志信息等業(yè)務(wù)。這個(gè)時(shí)候正常就是啟一個(gè)新線程去做一些業(yè)務(wù)處理,讓主線程異步的執(zhí)行其他業(yè)務(wù)。所以,本章節(jié)重點(diǎn)說下在SpringBoot中如何進(jìn)行異步調(diào)用及其相關(guān)知識和注意點(diǎn)。

一點(diǎn)知識

何為異步調(diào)用

異步調(diào)用前,我們說說它對應(yīng)的同步調(diào)用。通常開發(fā)過程中,一般上我們都是同步調(diào)用,即:程序按定義的順序依次執(zhí)行的過程,每一行代碼執(zhí)行過程必須等待上一行代碼執(zhí)行完畢后才執(zhí)行。而異步調(diào)用指:程序在執(zhí)行時(shí),無需等待執(zhí)行的返回值可繼續(xù)執(zhí)行后面的代碼。顯而易見,同步有依賴相關(guān)性,而異步?jīng)]有,所以異步可并發(fā)執(zhí)行,可提高執(zhí)行效率,在相同的時(shí)間做更多的事情。

題外話:處理異步、同步外,還有一個(gè)叫回調(diào)。其主要是解決異步方法執(zhí)行結(jié)果的處理方法,比如在希望異步調(diào)用結(jié)束時(shí)返回執(zhí)行結(jié)果,這個(gè)時(shí)候就可以考慮使用回調(diào)機(jī)制。

Async異步調(diào)用

SpringBoot中使用異步調(diào)用是很簡單的,只需要使用@Async注解即可實(shí)現(xiàn)方法的異步調(diào)用。

注意:需要在啟動(dòng)類加入@EnableAsync使異步調(diào)用@Async注解生效。

@SpringBootApplication
@EnableAsync
@Slf4j
public class Chapter21Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter21Application.class, args);
        log.info("Chapter21啟動(dòng)!");
    }
}

@Async異步調(diào)用

使用@Async很簡單,只需要在需要異步執(zhí)行的方法上加入此注解即可。這里創(chuàng)建一個(gè)控制層和一個(gè)服務(wù)層,進(jìn)行簡單示例下。

SyncService.java

@Component
public class SyncService {

    @Async
    public void asyncEvent() throws InterruptedException {
        //休眠1s
        Thread.sleep(1000);
        //log.info("異步方法輸出:{}!", System.currentTimeMillis());
    }

    public void syncEvent() throws InterruptedException {
        Thread.sleep(1000);
        //log.info("同步方法輸出:{}!", System.currentTimeMillis());
    }

}

控制層:AsyncController.java

@RestController
@Slf4j
public class AsyncController {

    @Autowired
    SyncService syncService;

    @GetMapping("/async")
    public String doAsync() throws InterruptedException {
        long start = System.currentTimeMillis();
        log.info("方法執(zhí)行開始:{}", start);
        //調(diào)用同步方法
        syncService.syncEvent();
        long syncTime = System.currentTimeMillis();
        log.info("同步方法用時(shí):{}", syncTime - start);
        //調(diào)用異步方法
        syncService.asyncEvent();
        long asyncTime = System.currentTimeMillis();
        log.info("異步方法用時(shí):{}", asyncTime - syncTime);
        log.info("方法執(zhí)行完成:{}!",asyncTime);
        return "async!!!";
    }
}

應(yīng)用啟動(dòng)后,可以看見控制臺輸出:

2018-08-16 22:21:35.949  INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController     : 方法執(zhí)行開始:1534429295949
2018-08-16 22:21:36.950  INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController     : 同步方法用時(shí):1001
2018-08-16 22:21:36.950  INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController     : 異步方法用時(shí):0
2018-08-16 22:21:36.950  INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController     : 方法執(zhí)行完成:1534429296950!
2018-08-16 22:21:37.950  INFO 17152 --- [cTaskExecutor-3] c.l.l.s.chapter21.service.SyncService    : 異步方法內(nèi)部線程名稱:SimpleAsyncTaskExecutor-3!

可以看出,調(diào)用異步方法時(shí),是立即返回的,基本沒有耗時(shí)。

這里有幾點(diǎn)需要注意下:

  1. 在默認(rèn)情況下,未設(shè)置TaskExecutor時(shí),默認(rèn)是使用SimpleAsyncTaskExecutor這個(gè)線程池,但此線程不是真正意義上的線程池,因?yàn)榫程不重用,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程?赏ㄟ^控制臺日志輸出可以看出,每次輸出線程名都是遞增的。
  2. 調(diào)用的異步方法,不能為同一個(gè)類的方法,簡單來說,因?yàn)?code>Spring在啟動(dòng)掃描時(shí)會(huì)為其創(chuàng)建一個(gè)代理類,而同類調(diào)用時(shí),還是調(diào)用本身的代理類的,所以和平常調(diào)用是一樣的。其他的注解如@Cache等也是一樣的道理,說白了,就是Spring的代理機(jī)制造成的。

自定義線程池

前面有提到,在默認(rèn)情況下,系統(tǒng)使用的是默認(rèn)的SimpleAsyncTaskExecutor進(jìn)行線程創(chuàng)建。所以一般上我們會(huì)自定義線程池來進(jìn)行線程的復(fù)用。

創(chuàng)建一個(gè)自定義的ThreadPoolTaskExecutor線程池:
Config.java

@Configuration
public class Config {

    /**
     * 配置線程池
     * @return
     */
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("oKong-");
        // 線程池對拒絕任務(wù)(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認(rèn)為后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

此時(shí),使用的是就只需要在@Async加入線程池名稱即可:

@Async("asyncPoolTaskExecutor")
    public void asyncEvent() throws InterruptedException {
        //休眠1s
        Thread.sleep(1000);
        log.info("異步方法內(nèi)部線程名稱:{}!", Thread.currentThread().getName());
    }

再次啟動(dòng)應(yīng)用,就可以看見已經(jīng)是使用自定義的線程了。

2018-08-16 22:32:02.676  INFO 4516 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 方法執(zhí)行開始:1534429922676
2018-08-16 22:32:03.681  INFO 4516 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 同步方法用時(shí):1005
2018-08-16 22:32:03.693  INFO 4516 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 異步方法用時(shí):12
2018-08-16 22:32:03.693  INFO 4516 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 方法執(zhí)行完成:1534429923693!
2018-08-16 22:32:04.694  INFO 4516 --- [        oKong-1] c.l.l.s.chapter21.service.SyncService    : 異步方法內(nèi)部線程名稱:oKong-1!

這里簡單說明下,關(guān)于ThreadPoolTaskExecutor參數(shù)說明:

  1. corePoolSize:線程池維護(hù)線程的最少數(shù)量
  2. keepAliveSeconds:允許的空閑時(shí)間,當(dāng)超過了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷毀
  3. maxPoolSize:線程池維護(hù)線程的最大數(shù)量,只有在緩沖隊(duì)列滿了之后才會(huì)申請超過核心線程數(shù)的線程
  4. queueCapacity:緩存隊(duì)列
  5. rejectedExecutionHandler:線程池對拒絕任務(wù)(無線程可用)的處理策略。這里采用了CallerRunsPolicy策略,當(dāng)線程池沒有處理能力的時(shí)候,該策略會(huì)直接在 execute 方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù);如果執(zhí)行程序已關(guān)閉,則會(huì)丟棄該任務(wù)。還有一個(gè)是AbortPolicy策略:處理程序遭到拒絕將拋出運(yùn)行時(shí)RejectedExecutionException。

而在一些場景下,若需要在關(guān)閉線程池時(shí)等待當(dāng)前調(diào)度任務(wù)完成后才開始關(guān)閉,可以通過簡單的配置,進(jìn)行優(yōu)雅的停機(jī)策略配置。關(guān)鍵就是通過setWaitForTasksToCompleteOnShutdown(true)setAwaitTerminationSeconds方法。

  • setWaitForTasksToCompleteOnShutdown:表明等待所有線程執(zhí)行完,默認(rèn)為false
  • setAwaitTerminationSeconds:等待的時(shí)間,因?yàn)椴荒軣o限的等待下去。

所以,線程池完整配置為:

@Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("oKong-");
        // 線程池對拒絕任務(wù)(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認(rèn)為后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //調(diào)度器shutdown被調(diào)用時(shí)等待當(dāng)前被調(diào)度的任務(wù)完成
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        //等待時(shí)長
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.initialize();
        return taskExecutor;
    }

異步回調(diào)及超時(shí)處理

對于一些業(yè)務(wù)場景下,需要異步回調(diào)的返回值時(shí),就需要使用異步回調(diào)來完成了。主要就是通過Future進(jìn)行異步回調(diào)。

異步回調(diào)

修改下異步方法的返回類型,加入Future

@Async("asyncPoolTaskExecutor")
public Future<String> asyncEvent() throws InterruptedException {
    //休眠1s
    Thread.sleep(1000);
    log.info("異步方法內(nèi)部線程名稱:{}!", Thread.currentThread().getName());
    return new AsyncResult<>("異步方法返回值");
}

其中AsyncResultSpring提供的一個(gè)Future接口的子類。

然后通過isDone方法,判斷是否已經(jīng)執(zhí)行完畢。

@GetMapping("/async")
    public String doAsync() throws InterruptedException {
        long start = System.currentTimeMillis();
        log.info("方法執(zhí)行開始:{}", start);
        //調(diào)用同步方法
        syncService.syncEvent();
        long syncTime = System.currentTimeMillis();
        log.info("同步方法用時(shí):{}", syncTime - start);
        //調(diào)用異步方法
        Future<String> doFutrue = syncService.asyncEvent();
        while(true) {
            //判斷異步任務(wù)是否完成
            if(doFutrue.isDone()) {
                break;
            }
            Thread.sleep(100);
        }
        long asyncTime = System.currentTimeMillis();
        log.info("異步方法用時(shí):{}", asyncTime - syncTime);
        log.info("方法執(zhí)行完成:{}!",asyncTime);
        return "async!!!";
    }

此時(shí),控制臺輸出:

2018-08-16 23:10:57.021  INFO 9072 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 方法執(zhí)行開始:1534431237020
2018-08-16 23:10:58.025  INFO 9072 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 同步方法用時(shí):1005
2018-08-16 23:10:59.037  INFO 9072 --- [        oKong-1] c.l.l.s.chapter21.service.SyncService    : 異步方法內(nèi)部線程名稱:oKong-1!
2018-08-16 23:10:59.040  INFO 9072 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 異步方法用時(shí):1015
2018-08-16 23:10:59.040  INFO 9072 --- [nio-8080-exec-1] c.l.l.s.c.controller.AsyncController     : 方法執(zhí)行完成:1534431239040!

所以,當(dāng)某個(gè)業(yè)務(wù)功能可以同時(shí)拆開一起執(zhí)行時(shí),可利用異步回調(diào)機(jī)制,可有效的減少程序執(zhí)行時(shí)間,提高效率。

超時(shí)處理

對于一些需要異步回調(diào)的函數(shù),不能無期限的等待下去,所以一般上需要設(shè)置超時(shí)時(shí)間,超時(shí)后可將線程釋放,而不至于一直堵塞而占用資源。

對于Future配置超時(shí),很簡單,通過get方法即可,具體如下:

//get方法會(huì)一直堵塞,直到等待執(zhí)行完成才返回
//get(long timeout, TimeUnit unit) 在設(shè)置時(shí)間類未返回結(jié)果,會(huì)直接排除異常TimeoutException,messages為null
String result = doFutrue.get(60, TimeUnit.SECONDS);//60s

超時(shí)后,會(huì)拋出異常TimeoutException類,此時(shí)可進(jìn)行統(tǒng)一異常捕獲即可。

超時(shí)異常

參考資料

  1. https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#scheduling-annotation-support
  2. https://www.cnblogs.com/cz123/p/7693064.html

總結(jié)

本章節(jié)主要是講解了異步請求的使用及相關(guān)配置,如超時(shí),異常等處理。在剝離一些和業(yè)務(wù)無關(guān)的操作時(shí),就可以考慮使用異步調(diào)用進(jìn)行其他無關(guān)業(yè)務(wù)操作,以此提供業(yè)務(wù)的處理效率;蛘咭恍I(yè)務(wù)場景下可拆分出多個(gè)方法進(jìn)行同步執(zhí)行又互不影響時(shí),也可以考慮使用異步調(diào)用方式提供執(zhí)行效率。既然已經(jīng)講解了異步相關(guān)知識,下一章節(jié)就來介紹下定時(shí)任務(wù)的使用。

最后

目前互聯(lián)網(wǎng)上很多大佬都有SpringBoot系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實(shí)踐的。若文中有所錯(cuò)誤之處,還望提出,謝謝。

標(biāo)簽: 代碼 互聯(lián)網(wǎng)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:SpringBoot | 番外:使用小技巧合集

下一篇:SpringBoot | 第二十章:異步開發(fā)之異步請求