180803-Spring定時(shí)任務(wù)高級(jí)使用篇

logo

Spring定時(shí)任務(wù)高級(jí)使用篇

前面一篇博文 《Spring之定時(shí)任務(wù)基本使用篇》 介紹了Spring環(huán)境下,定時(shí)任務(wù)的簡(jiǎn)單使用姿勢(shì),也留了一些問題,這一篇?jiǎng)t希望能針對(duì)這些問題給個(gè)答案

I. 定時(shí)任務(wù)進(jìn)階篇

1. 問題小結(jié)

前面一篇博文,拋出了下面的幾個(gè)問題,接下來則圍繞問題進(jìn)行分析

  • 一個(gè)項(xiàng)目中有多個(gè)定時(shí)任務(wù)時(shí),他們是并行執(zhí)行的還是串行執(zhí)行的?
  • 如果默認(rèn)是串行的
    • 那么有相同的crond表達(dá)式的定時(shí)任務(wù)之間,有先后順序么?
    • 某個(gè)任務(wù)的阻塞是否會(huì)影響后面的任務(wù)?
    • 如果需要他們并行執(zhí)行,可以怎么做?
  • 如果是并發(fā)執(zhí)行的
    • 是新創(chuàng)建線程還是采用線程池來復(fù)用呢?
    • 在并發(fā)執(zhí)行時(shí),假設(shè)有個(gè)每秒執(zhí)行一次的任務(wù),但是它執(zhí)行一次消耗的時(shí)間大于1s時(shí),這個(gè)任務(wù)的表現(xiàn)時(shí)怎樣的呢?不斷地新增線程來執(zhí)行還是等執(zhí)行完畢之后再執(zhí)行下一次的呢?

2. 多定時(shí)任務(wù)的串并行分析

如何確認(rèn)一個(gè)項(xiàng)目中的多個(gè)定時(shí)任務(wù)是串行執(zhí)行還是并發(fā)執(zhí)行呢?要想驗(yàn)證這個(gè)功能,最好的法子就是寫個(gè)testcase,比如定義兩個(gè)定時(shí)任務(wù),在其中一個(gè)任務(wù)中寫個(gè)死循環(huán),看另外一個(gè)任務(wù)是否會(huì)正常執(zhí)行

@Scheduled(cron = "0/1 * * * * ?")
public void sc1() throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
    while (true) {
        Thread.sleep(5000);
    }
}

@Scheduled(cron = "0/1 * * * * ?")
public void sc2() {
    System.out.println(Thread.currentThread().getName() + " | sc2 " + System.currentTimeMillis());
}

首先我們分析的是 sc1和sc2這兩個(gè)任務(wù)的執(zhí)行是串行還是并行的,暫時(shí)先不考慮 sc1 調(diào)用時(shí)阻塞,下一秒是否是開新的線程再調(diào)用sc1

  • 若串行:則sc1打印一次,sc2可能打印0或者1次
  • 若并行:sc1打印一次,sc2打印n多次

實(shí)際運(yùn)行,GIF圖演示如下

sch01.gif

上圖的結(jié)果,印證了默認(rèn)的情況下,多個(gè)定時(shí)任務(wù)時(shí)串行執(zhí)行的;如果一個(gè)任務(wù)出現(xiàn)阻塞,其他的任務(wù)都會(huì)受到影響

3. 定時(shí)任務(wù)執(zhí)行的優(yōu)先級(jí)

既然是順序執(zhí)行的,那么優(yōu)先級(jí)怎么定?每次都是固定的,還是隨機(jī)的呢?

要驗(yàn)證上面的方法,也容易,同樣兩個(gè)任務(wù),看他們的輸出是否會(huì)亂掉,如果每次都是任務(wù)1打印完再打印任務(wù)2,那就是固定優(yōu)先級(jí)的;否則每次調(diào)度時(shí),順序不好說

測(cè)試代碼如下

@Scheduled(cron = "0/1 * * * * ?")
public void sc1()  {
    System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
}

@Scheduled(cron = "0/1 * * * * ?")
public void sc2() {
    System.out.println(Thread.currentThread().getName() + " | sc2 " + System.currentTimeMillis());
}

實(shí)測(cè)結(jié)果如下

sch02.jpg

從輸出得出結(jié)論:順序是串掉的,并沒有表現(xiàn)出明顯的優(yōu)先級(jí)關(guān)系

4. 并行調(diào)度

接下來的問題就是我希望這些任務(wù)可以并發(fā)執(zhí)行,可以實(shí)現(xiàn)么?

當(dāng)然是可以,用起來也比較簡(jiǎn)單,首先是在Application上添加注解@EnableAsync,開啟異步調(diào)用,然后再計(jì)劃任務(wù)上加上@Async注解即可,一個(gè)簡(jiǎn)單的demo如下

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class QuickMediaApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuickMediaApplication.class, args);
    }

    @Scheduled(cron = "0/1 * * * * ?")
    @Async
    public void sc1()  {
        System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
    }
}

上面執(zhí)行之后,查看輸出(異步調(diào)度時(shí),理論上線程名應(yīng)該不一樣)

sch03.jpg

從上面的輸出,可以簡(jiǎn)單的推理,每次調(diào)度上面的任務(wù)都是新開了一個(gè)線程來做的,所以如果在定時(shí)任務(wù)中寫了死循環(huán),是否會(huì)導(dǎo)致無限線程,最后整個(gè)進(jìn)程崩掉?

額外提一句,linux系統(tǒng)下單進(jìn)程的線程數(shù)是有上線的,查看命令為:

ulimit -u

在測(cè)試之前,先看下上面的正常任務(wù)執(zhí)行,如下面的動(dòng)圖,線程數(shù)并沒有夸張的長(zhǎng)法

sch04.gif

接下來?yè)Q成死循環(huán)的調(diào)度方式,實(shí)際測(cè)試如下,線程數(shù)蹭蹭的上漲

sch05.gif

所以使用默認(rèn)的異步調(diào)用方式,并不是一個(gè)好注意,說不準(zhǔn)就被玩死了自己都不知道,那么可以用自己的線程池來管理這些異步任務(wù)么?

5. 自定義線程池

用自定義的線程池來取代默認(rèn)線程管理方式,無疑是一個(gè)更加安全和靈活的方式,使用起來也并不麻煩,和平常創(chuàng)建線程池的套路沒什么區(qū)別,要在Spring生態(tài)中使用,就把它搞成bean即可

直接借助Spring的線程池ThreadPoolTaskExecutor

@Bean
public AsyncTaskExecutor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setThreadNamePrefix("yhh-schedule-");
    executor.setMaxPoolSize(10);
    executor.setCorePoolSize(3);
    executor.setQueueCapacity(0);
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    return executor;
}

@Scheduled(cron = "0/1 * * * * ?")
@Async
public void sc1() throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + " | sc1 " + System.currentTimeMillis());
    while (true) {
        Thread.sleep(1000 * 5);
    }
}

實(shí)際演示的結(jié)果如下,最多10個(gè)線程,再提交的任務(wù)直接丟棄

sch06.gif

簡(jiǎn)單說一下,用自定義線程池的好處:

  • 合理的分配線程池參數(shù)
  • 拒絕策略的選擇也比較有意思(可以按照自己的想法來處理"負(fù)載"的任務(wù))
  • 線程池命名,對(duì)于以后問題排查,會(huì)有很大的幫助

6. 小結(jié)

本來這篇博文在昨天即8月2號(hào)就應(yīng)該寫完的,結(jié)果晚上生產(chǎn)環(huán)境下除了點(diǎn)問題,解決線上故障之后就比較晚了,留到了今天,哎,拖延癥也是要不得。。。

下面小結(jié)Spring中定時(shí)任務(wù)的幾個(gè)知識(shí)點(diǎn)

  • 默認(rèn)所有的定時(shí)任務(wù)都是串行調(diào)度的,一個(gè)線程,且即便crond完全相同的兩個(gè)任務(wù)先后順序也沒法保證(具體原因需要源碼分析,看下這塊是怎么支持)
  • 使用@Async注解可以使定時(shí)任務(wù)異步調(diào)度;但是需要開啟配置,在啟動(dòng)類上添加 @EnableAsync 注解
  • 開啟并發(fā)執(zhí)行時(shí),推薦用自定義的線程池來替代默認(rèn)的,理由見上面

II. 其他

0. 相關(guān)

1. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的個(gè)人博客,記錄所有學(xué)習(xí)和工作中的博文,歡迎大家前去逛逛

2. 聲明

盡信書則不如,已上內(nèi)容,純屬一家之言,因個(gè)人能力有限,難免有疏漏和錯(cuò)誤之處,如發(fā)現(xiàn)bug或者有更好的建議,歡迎批評(píng)指正,不吝感激

3. 掃描關(guān)注

小灰灰Blog&公眾號(hào)

QrCode

知識(shí)星球

zhishi
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,694評(píng)論 19 139
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,282評(píng)論 8 265
  • 博客原文 徒手翻譯spring framework 4.2.3官方文檔的第33章,若有翻譯不當(dāng)之處請(qǐng)指正。 定時(shí)任...
    rabbitGYK閱讀 5,854評(píng)論 4 24
  • iOS多線程編程 基本知識(shí) 1. 進(jìn)程(process) 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)...
    陵無山閱讀 6,364評(píng)論 1 14
  • 20175.18 晴 星期四 心情:滿滿的激情 欣賞自己:每天參加各種培訓(xùn),雖然累,但也很開心,因?yàn)槊刻於寄?..
    蓉媛閱讀 242評(píng)論 0 0

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