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

異步調(diào)用介紹

  1. 異步調(diào)用
    異步調(diào)用就是在不阻塞主線程的情況下執(zhí)行高耗時(shí)方法
  2. 常規(guī)異步
    通過開啟新線程實(shí)現(xiàn)
  3. 在Springboot中啟用異步方法
    需要4個(gè)注解
    @EnableAsync 開啟異步
    @Component 注冊異步組件
    @Async 標(biāo)注異步方法
    @Autowired 注入異步組件
  4. 進(jìn)行一次異步調(diào)用
    首先在一個(gè)Config類上標(biāo)注開啟異步
    然后創(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是沒用的。
    在Async 方法調(diào)用的Service上標(biāo)注@Transactional 有效霜大。
  6. 異步方法的內(nèi)部調(diào)用
    異步方法不支持內(nèi)部調(diào)用构哺,也就是異步方法不能寫在需要調(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)原理是通過注入一個(gè)代理類到Bean中囊卜,這個(gè)代理繼承這個(gè)Bean娜扇,需要覆寫異步方法并執(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è)方法做的事情很簡單: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請求的服務(wù)脱吱,里面做的事情其實(shí)是同步的,接下來我們就開始配置springboot的線程池服務(wù)认罩,將service層做的事情都提交到線程池中去處理箱蝠;

springboot的線程池配置

創(chuàng)建一個(gè)配置類ExecutorConfig,用來定義如何創(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)用者所在的線程來執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //執(zhí)行初始化
        executor.initialize();
        return executor;
    }
}

注意页慷,上面的方法名稱為asyncServiceExecutor憔足,稍后馬上用到胁附;

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

打開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)行起來(pom.xml所在文件夾下執(zhí)行mvn spring-boot:run)揭绑;
  2. 在瀏覽器輸入:http://localhost:8080弓候;
  3. 在瀏覽器用F5按鈕快速多刷新幾次;
  4. 在springboot的控制臺(tái)看見日志如下:
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í)行了撰筷,并且每次請求中,controller的起始和結(jié)束日志都是連續(xù)打印的畦徘,表明每次請求都快速響應(yīng)了毕籽,而耗時(shí)的操作都留給線程池中的線程去異步執(zhí)行;

擴(kuò)展ThreadPoolTaskExecutor

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

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ì)列大小都打印出來了包雀,然后Override了父類的execute、submit等方法亲铡,在里面調(diào)用showThreadPoolInfo方法才写,這樣每次有任務(wù)被提交到線程池的時(shí)候,都會(huì)將當(dāng)前線程池的基本情況打印到日志中奖蔓;

修改ExecutorConfig.java的asyncServiceExecutor方法赞草,將ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()改為ThreadPoolTaskExecutor 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)用者所在的線程來執(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]

這說明提交任務(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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雹姊,隨后出現(xiàn)的幾起案子股缸,更是在濱河造成了極大的恐慌,老刑警劉巖吱雏,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敦姻,死亡現(xiàn)場離奇詭異,居然都是意外死亡歧杏,警方通過查閱死者的電腦和手機(jī)镰惦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犬绒,“玉大人旺入,你說我怎么就攤上這事】Γ” “怎么了茵瘾?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咐鹤。 經(jīng)常有香客問我拗秘,道長,這世上最難降的妖魔是什么慷暂? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任聘殖,我火速辦了婚禮,結(jié)果婚禮上行瑞,老公的妹妹穿的比我還像新娘奸腺。我一直安慰自己,他們只是感情好血久,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布突照。 她就那樣靜靜地躺著,像睡著了一般氧吐。 火紅的嫁衣襯著肌膚如雪讹蘑。 梳的紋絲不亂的頭發(fā)上末盔,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音座慰,去河邊找鬼陨舱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛版仔,可吹牛的內(nèi)容都是我干的游盲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛮粮,長吁一口氣:“原來是場噩夢啊……” “哼益缎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起然想,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤莺奔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后变泄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體令哟,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年杖刷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了励饵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滑燃,死狀恐怖役听,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情表窘,我是刑警寧澤典予,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站乐严,受9級(jí)特大地震影響瘤袖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昂验,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一捂敌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧既琴,春花似錦占婉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春奖慌,著一層夾襖步出監(jiān)牢的瞬間抛虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國打工简僧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留建椰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓涎劈,卻偏偏與公主長得像广凸,于是被迫代替她去往敵國和親阅茶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛛枚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

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