SpringBoot線程池異步調(diào)用

異步調(diào)用介紹

  1. 異步調(diào)用
    異步調(diào)用就是在不阻塞主線程的情況下執(zhí)行高耗時(shí)方法
  2. 常規(guī)異步
    通過(guò)開(kāi)啟新線程實(shí)現(xiàn)
  3. 在Springboot中啟用異步方法
    需要4個(gè)注解
    @EnableAsync 開(kāi)啟異步
    @Component 注冊(cè)異步組件
    @Async 標(biāo)注異步方法
    @Autowired 注入異步組件
  4. 進(jìn)行一次異步調(diào)用
    首先在一個(gè)Config類上標(biāo)注開(kāi)啟異步
    然后創(chuàng)建一個(gè)異步的組件類,就跟Service,Controller 一樣一樣的,用Component標(biāo)注,Service也行
    在類內(nèi)創(chuàng)建一個(gè)異步方法,打上Async 標(biāo)記。這個(gè)方法必須是實(shí)例方法。
    然后就跟注入Service一樣一樣的了。
  5. 異步事務(wù)
    在Async 方法上標(biāo)注@Transactional是沒(méi)用的。
    在Async 方法調(diào)用的Service上標(biāo)注@Transactional 有效。
  6. 異步方法的內(nèi)部調(diào)用
    異步方法不支持內(nèi)部調(diào)用,也就是異步方法不能寫(xiě)在需要調(diào)用他的類的內(nèi)部。
    比如Class A 有a,b,c。b有Async標(biāo)注。此時(shí)a對(duì)b的異步調(diào)用是失效的。
  7. 為什么異步方法必須是實(shí)例方法
    因?yàn)閟tatic方法不能被Override。因?yàn)锧Async 異步方法的實(shí)現(xiàn)原理是通過(guò)注入一個(gè)代理類到Bean中,這個(gè)代理繼承這個(gè)Bean,需要覆寫(xiě)異步方法并執(zhí)行。
    然后這個(gè)東西,會(huì)被Spring放到自己維護(hù)的一個(gè)隊(duì)列中。等待線程池讀取并執(zhí)行。

線程池的使用

創(chuàng)建Service層的接口和實(shí)現(xiàn)

創(chuàng)建一個(gè)service層的接口AsyncService,如下:

public interface AsyncService {

    /**
     * 執(zhí)行異步任務(wù)
     */
    void executeAsync();
}

對(duì)應(yīng)的AsyncServiceImpl,實(shí)現(xiàn)如下:

@Service
public class AsyncServiceImpl implements AsyncService {

    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

    @Override
    public void executeAsync() {
        logger.info("start executeAsync");
        try{
            Thread.sleep(1000);
        }catch(Exception e){
            e.printStackTrace();
        }
        logger.info("end executeAsync");
    }
}

這個(gè)方法做的事情很簡(jiǎn)單:sleep了一秒鐘;

創(chuàng)建controller

創(chuàng)建一個(gè)controller為Hello,里面定義一個(gè)http接口,做的事情是調(diào)用Service層的服務(wù),如下:

@RestController
public class Hello {

    private static final Logger logger = LoggerFactory.getLogger(Hello.class);

    @Autowired
    private AsyncService asyncService;

    @RequestMapping("/")
    public String submit(){
        logger.info("start submit");

        //調(diào)用service層的任務(wù)
        asyncService.executeAsync();

        logger.info("end submit");

        return "success";
    }
}

至此,我們已經(jīng)做好了一個(gè)http請(qǐng)求的服務(wù),里面做的事情其實(shí)是同步的,接下來(lái)我們就開(kāi)始配置springboot的線程池服務(wù),將service層做的事情都提交到線程池中去處理;

springboot的線程池配置

創(chuàng)建一個(gè)配置類ExecutorConfig,用來(lái)定義如何創(chuàng)建一個(gè)ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync這兩個(gè)注解,表示這是個(gè)配置類,并且是線程池的配置類,如下所示:

@Configuration
@EnableAsync
public class ExecutorConfig {

    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

    @Bean
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(5);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(5);
        //配置隊(duì)列大小
        executor.setQueueCapacity(99999);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix("async-service-");

        // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候,如何處理新任務(wù)
        // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來(lái)執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //執(zhí)行初始化
        executor.initialize();
        return executor;
    }
}

注意,上面的方法名稱為asyncServiceExecutor,稍后馬上用到;

將Service層的服務(wù)異步化

打開(kāi)AsyncServiceImpl.java,在executeAsync方法上增加注解@Async(“asyncServiceExecutor”),asyncServiceExecutor是前面ExecutorConfig.java中的方法名,表明executeAsync方法進(jìn)入的線程池是asyncServiceExecutor方法創(chuàng)建的,如下:

@Override
    @Async("asyncServiceExecutor")
    public void executeAsync() {
        logger.info("start executeAsync");
        try{
            Thread.sleep(1000);
        }catch(Exception e){
            e.printStackTrace();
        }
        logger.info("end executeAsync");
    }

驗(yàn)證效果

  1. 將這個(gè)springboot運(yùn)行起來(lái)(pom.xml所在文件夾下執(zhí)行mvn spring-boot:run);
  2. 在瀏覽器輸入:http://localhost:8080;
  3. 在瀏覽器用F5按鈕快速多刷新幾次;
  4. 在springboot的控制臺(tái)看見(jiàn)日志如下:
2018-01-21 22:43:18.630  INFO 14824 --- [nio-8080-exec-8] c.b.t.controller.Hello                   : start submit
2018-01-21 22:43:18.630  INFO 14824 --- [nio-8080-exec-8] c.b.t.controller.Hello                   : end submit
2018-01-21 22:43:18.929  INFO 14824 --- [async-service-1] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:18.930  INFO 14824 --- [async-service-1] c.b.t.service.impl.AsyncServiceImpl      : start executeAsync
2018-01-21 22:43:19.005  INFO 14824 --- [async-service-2] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:19.006  INFO 14824 --- [async-service-2] c.b.t.service.impl.AsyncServiceImpl      : start executeAsync
2018-01-21 22:43:19.175  INFO 14824 --- [async-service-3] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:19.175  INFO 14824 --- [async-service-3] c.b.t.service.impl.AsyncServiceImpl      : start executeAsync
2018-01-21 22:43:19.326  INFO 14824 --- [async-service-4] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:19.495  INFO 14824 --- [async-service-5] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:19.930  INFO 14824 --- [async-service-1] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:20.006  INFO 14824 --- [async-service-2] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 22:43:20.191  INFO 14824 --- [async-service-3] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync

如上日志所示,我們可以看到controller的執(zhí)行線程是”nio-8080-exec-8”,這是tomcat的執(zhí)行線程,而service層的日志顯示線程名為“async-service-1”,顯然已經(jīng)在我們配置的線程池中執(zhí)行了,并且每次請(qǐng)求中,controller的起始和結(jié)束日志都是連續(xù)打印的,表明每次請(qǐng)求都快速響應(yīng)了,而耗時(shí)的操作都留給線程池中的線程去異步執(zhí)行;

擴(kuò)展ThreadPoolTaskExecutor

雖然我們已經(jīng)用上了線程池,但是還不清楚線程池當(dāng)時(shí)的情況,有多少線程在執(zhí)行,多少在隊(duì)列中等待呢?這里我創(chuàng)建了一個(gè)ThreadPoolTaskExecutor的子類,在每次提交線程的時(shí)候都會(huì)將當(dāng)前線程池的運(yùn)行狀況打印出來(lái),代碼如下:

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix){
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if(null==threadPoolExecutor){
            return;
        }

        logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1\. do execute");
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2\. do execute");
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1\. do submit");
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2\. do submit");
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1\. do submitListenable");
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2\. do submitListenable");
        return super.submitListenable(task);
    }
}

如上所示,showThreadPoolInfo方法中將任務(wù)總數(shù)、已完成數(shù)、活躍線程數(shù),隊(duì)列大小都打印出來(lái)了,然后Override了父類的execute、submit等方法,在里面調(diào)用showThreadPoolInfo方法,這樣每次有任務(wù)被提交到線程池的時(shí)候,都會(huì)將當(dāng)前線程池的基本情況打印到日志中;

修改ExecutorConfig.java的asyncServiceExecutor方法,將ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()改為T(mén)hreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor(),如下所示:

@Bean
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        //使用VisiableThreadPoolTaskExecutor
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(5);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(5);
        //配置隊(duì)列大小
        executor.setQueueCapacity(99999);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix("async-service-");

        // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候,如何處理新任務(wù)
        // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來(lái)執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //執(zhí)行初始化
        executor.initialize();
        return executor;
    }

再次啟動(dòng)該工程,再瀏覽器反復(fù)刷新http://localhost:8080,看到的日志如下:

2018-01-21 23:04:56.113  INFO 15580 --- [nio-8080-exec-1] c.b.t.e.VisiableThreadPoolTaskExecutor   : async-service-, 2. do submit,taskCount [99], completedTaskCount [85], activeCount [5], queueSize [9]
2018-01-21 23:04:56.113  INFO 15580 --- [nio-8080-exec-1] c.b.t.controller.Hello                   : end submit
2018-01-21 23:04:56.225  INFO 15580 --- [async-service-1] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 23:04:56.225  INFO 15580 --- [async-service-1] c.b.t.service.impl.AsyncServiceImpl      : start executeAsync
2018-01-21 23:04:56.240  INFO 15580 --- [nio-8080-exec-2] c.b.t.controller.Hello                   : start submit
2018-01-21 23:04:56.240  INFO 15580 --- [nio-8080-exec-2] c.b.t.e.VisiableThreadPoolTaskExecutor   : async-service-, 2. do submit,taskCount [100], completedTaskCount [86], activeCount [5], queueSize [9]
2018-01-21 23:04:56.240  INFO 15580 --- [nio-8080-exec-2] c.b.t.controller.Hello                   : end submit
2018-01-21 23:04:56.298  INFO 15580 --- [async-service-2] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 23:04:56.298  INFO 15580 --- [async-service-2] c.b.t.service.impl.AsyncServiceImpl      : start executeAsync
2018-01-21 23:04:56.372  INFO 15580 --- [nio-8080-exec-3] c.b.t.controller.Hello                   : start submit
2018-01-21 23:04:56.373  INFO 15580 --- [nio-8080-exec-3] c.b.t.e.VisiableThreadPoolTaskExecutor   : async-service-, 2. do submit,taskCount [101], completedTaskCount [87], activeCount [5], queueSize [9]
2018-01-21 23:04:56.373  INFO 15580 --- [nio-8080-exec-3] c.b.t.controller.Hello                   : end submit
2018-01-21 23:04:56.444  INFO 15580 --- [async-service-3] c.b.t.service.impl.AsyncServiceImpl      : end executeAsync
2018-01-21 23:04:56.445  INFO 15580 --- [async-service-3] c.b.t.service.impl.AsyncServiceImpl      : start executeAsync

注意這一行日志:2. do submit,taskCount [101], completedTaskCount [87], activeCount [5], queueSize [9]

這說(shuō)明提交任務(wù)到線程池的時(shí)候,調(diào)用的是submit(Callable task)這個(gè)方法,當(dāng)前已經(jīng)提交了101個(gè)任務(wù),完成了87個(gè),當(dāng)前有5個(gè)線程在處理任務(wù),還剩9個(gè)任務(wù)在隊(duì)列中等待,線程池的基本情況一路了然;

至此,springboot線程池服務(wù)的實(shí)戰(zhàn)就完成了,希望能幫您在工程中快速實(shí)現(xiàn)異步服務(wù);

參考文章
Springboot @Async 異步方法
springboot線程池的使用和擴(kuò)展

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,194評(píng)論 8 265
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,621評(píng)論 1 32
  • 我在網(wǎng)上看過(guò)這樣一段話:我們努力工作為的就是站在我愛(ài)的人身邊,不管他是富甲一方,還是一無(wú)所有,我都可以張開(kāi)雙臂坦然...
    球球嘟咕嚕閱讀 205評(píng)論 0 1
  • 國(guó)破山河在,氣量大的人,自然能夠順應(yīng)時(shí)代而記下新篇章,從來(lái)不會(huì)糾結(jié)過(guò)去,只想未來(lái),做好必要的規(guī)劃,有句話說(shuō)好,“自...
    換氧閱讀 219評(píng)論 0 1

友情鏈接更多精彩內(nèi)容