實(shí)戰(zhàn)Spring Boot 2.0系列(六) - 單機(jī)定時(shí)任務(wù)的幾種實(shí)現(xiàn)

前言

定時(shí)任務(wù) 一般會(huì)存在 中大型企業(yè)級 項(xiàng)目中,為了減少 服務(wù)器瞻想、數(shù)據(jù)庫 的壓力咆耿,往往會(huì)以 定時(shí)任務(wù) 的方式去完成某些業(yè)務(wù)邏輯。

常見的就是 金融服務(wù)系統(tǒng) 推送回調(diào)故硅,一般支付系統(tǒng)訂單在沒有收到成功的回調(diào)返回內(nèi)容時(shí)會(huì) 持續(xù)性的回調(diào)外遇,這種回調(diào)一般都是 定時(shí)任務(wù) 來完成。

還有就是 報(bào)表的生成契吉,我們一般會(huì)在客戶 訪問量小 時(shí)完成這個(gè)操作跳仿,也可以采用 定時(shí)任務(wù) 來完成。

image

正文

定時(shí)任務(wù)的幾種方式

Timer

這是 Java 自帶的 java.util.Timer 類捐晶,這個(gè)類允許調(diào)度一個(gè)名為 java.util.TimerTask 任務(wù)菲语。使用這種方式可以讓你的程序按照某一個(gè) 頻度 執(zhí)行妄辩,但不能在 指定時(shí)間 運(yùn)行。現(xiàn)在一般用的較少山上。

ScheduledExecutorService

JDK 自帶的一個(gè)類眼耀,是基于 線程池 設(shè)計(jì)的定時(shí)任務(wù)類,每個(gè) 調(diào)度任務(wù) 都會(huì)分配到 線程池 中的一個(gè) 線程 去執(zhí)行佩憾。也就是說哮伟,任務(wù)是 并發(fā)執(zhí)行,互不影響的妄帘。

Spring Task

Spring 3.0 以后自帶的 Task楞黄,支持 多線程 調(diào)度,可以將它看成一個(gè) 輕量級Quartz抡驼,而且使用起來比 Quartz 簡單許多鬼廓,但是適用于 單節(jié)點(diǎn)定時(shí)任務(wù)調(diào)度

Quartz

這是一個(gè) 功能比較強(qiáng)大 的的調(diào)度器致盟,可以讓你的程序在指定時(shí)間執(zhí)行碎税,也可以按照某一個(gè)頻度執(zhí)行,配置起來 稍顯復(fù)雜馏锡。Quartz 功能強(qiáng)大雷蹂,可以結(jié)合 數(shù)據(jù)庫持久化,進(jìn)行 分布式任務(wù)延時(shí)調(diào)度杯道。

Cron表達(dá)式簡介

Cron 表達(dá)式是一個(gè)字符串匪煌,字符串以 56 個(gè) 空格 隔開,分為 67 個(gè) 蕉饼,每一個(gè)域代表一個(gè)含義虐杯,Cron 有如下兩種語法格式:

  1. Seconds Minutes Hours DayofMonth Month DayofWeek Year
  2. Seconds Minutes Hours DayofMonth Month DayofWeek

每個(gè)域?qū)?yīng)的含義、域值范圍和特殊表示符昧港,從左到右依次如下:

字段 允許值 允許的特殊字符
0-59 , - * /
0-59 , - * /
小時(shí) 0-23 , - * /
日期 1-31 , - * / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * / L C #
年(可選) 留空, 1970-2099 , - * /

如上面的表達(dá)式所示:

  • ""字符: 被用來指定所有的值擎椰。如:在分鐘的字段域里表示"每分鐘"。

  • "-"字符: 被用來指定一個(gè)范圍创肥。如:"10-12" 在小時(shí)域意味著 "10點(diǎn)达舒、11點(diǎn)、12點(diǎn)"叹侄。

  • ","字符: 被用來指定另外的值巩搏。如:"MON,WED,FRI" 在星期域里表示 "星期一、星期三趾代、星期五"贯底。

  • "?"字符: 只在日期域和星期域中使用。它被用來指定"非明確的值"撒强。當(dāng)你需要通過在這兩個(gè)域中的一個(gè)來指定一些東西的時(shí)候禽捆,它是有用的笙什。看下面的例子你就會(huì)明白胚想。

  • "L"字符: 指定在月或者星期中的某天(最后一天)琐凭。即 "Last" 的縮寫。但是在星期和月中 "L" 表示不同的意思浊服,如:在月子段中 "L" 指月份的最后一天 - 1月31日统屈,2月28日。

    • 如果在星期字段中則簡單的表示為 "7" 或者 "SAT" 字符牙躺。
    • 如果在星期字段中在某個(gè) value 值得后面愁憔,則表示 "某月的最后一個(gè)星期value",如 "6L" 表示某月的最后一個(gè)星期五述呐。
  • "W"字符: 只能用在月份字段中惩淳,該字段指定了離指定日期最近的那個(gè)星期日蕉毯。

  • "#"字符: 只能用在星期字段乓搬,該字段指定了第幾個(gè)星期 value 在某月中

每一個(gè)元素都可以顯式地規(guī)定一個(gè)值(如 6),一個(gè)區(qū)間(如 9-12)代虾,一個(gè)列表(如 9进肯,11,13)或一個(gè)通配符(如 *)棉磨。"月份中的日期""星期中的日期" 這兩個(gè)元素是 互斥的江掩,因此應(yīng)該通過設(shè)置一個(gè) 問號?)來表明你不想設(shè)置的那個(gè)字段。下表顯示了一些 cron 表達(dá)式的 例子 和它們的意義:

表達(dá)式 意義
"0 0 12 * * ?" 每天中午12點(diǎn)觸發(fā)
"0 15 10 ? * *" 每天上午10:15觸發(fā)
"0 15 10 * * ?" 每天上午10:15觸發(fā)
"0 15 10 * * ? *" 每天上午10:15觸發(fā)
"0 15 10 * * ? 2005" 2005年的每天上午10:15觸發(fā)
"0 * 14 * * ?" 在每天下午2點(diǎn)到下午2:59期間的每1分鐘觸發(fā)
"0 0/5 14 * * ?" 在每天下午2點(diǎn)到下午2:55期間的每5分鐘觸發(fā)
"0 0/5 14,18 * * ?" 在每天下午2點(diǎn)到2:55期間和下午6點(diǎn)到6:55期間的每5分鐘觸發(fā)
"0 0-5 14 * * ?" 在每天下午2點(diǎn)到下午2:05期間的每1分鐘觸發(fā)
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發(fā)
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15觸發(fā)
"0 15 10 15 * ?" 每月15日上午10:15觸發(fā)
"0 15 10 L * ?" 每月最后一日的上午10:15觸發(fā)
"0 15 10 ? * 6L" 每月的最后一個(gè)星期五上午10:15觸發(fā)
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一個(gè)星期五上午10:15觸發(fā)
"0 15 10 ? * 6#3" 每月的第三個(gè)星期五上午10:15觸發(fā)
0 6 * * * 每天早上6點(diǎn)
0 /2 * * 每兩個(gè)小時(shí)
0 23-7/2乘瓤,8 * * * 晚上11點(diǎn)到早上8點(diǎn)之間每兩個(gè)小時(shí)环形,早上八點(diǎn)
0 11 4 * 1-3 每個(gè)月的4號和每個(gè)禮拜的禮拜一到禮拜三的早上11點(diǎn)
0 4 1 1 * 1月1日早上4點(diǎn)

環(huán)境準(zhǔn)備

配置gradle依賴

利用 Spring Initializer 創(chuàng)建一個(gè) gradle 項(xiàng)目 spring-boot-scheduler-task-management,創(chuàng)建時(shí)添加相關(guān)依賴衙傀。得到的初始 build.gradle 如下:

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.ostenant.springboot.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Spring Boot 入口類上配置 @EnableScheduling 注解開啟 Spring 自帶的定時(shí)處理功能抬吟。

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

配置Timer任務(wù)

這個(gè) API 目前在項(xiàng)目中很少用,直接給出示例代碼统抬。具體的介紹可以查看 API火本。Timer 的內(nèi)部只有 一個(gè)線程,如果有 多個(gè)任務(wù) 的話就會(huì) 順序執(zhí)行聪建,這樣任務(wù)的 延遲時(shí)間循環(huán)時(shí)間 就會(huì)出現(xiàn)問題钙畔。

TimerService.java

public class TimerService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TimerService.class);
    private AtomicLong counter = new AtomicLong();

    public void schedule() {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                long count = counter.incrementAndGet();
                LOGGER.info("Schedule timerTask {} times", count);
            }
        };
        Timer timer = new Timer();
        timer.schedule(timerTask, 1000L, 10 * 1000L;
    }
}

上面的代碼定義了一個(gè) TimerTask,在 TimerTask 中累加 執(zhí)行次數(shù)金麸,并通過 slf4j 進(jìn)行打印 (自帶執(zhí)行時(shí)間)擎析。然后通過 Timer 調(diào)度工具類調(diào)度 TimerTask 任務(wù),設(shè)置 初始化延遲時(shí)間1s挥下,定時(shí)執(zhí)行間隔10s揍魂,測試代碼如下:

public static void main(String[] args) {
    TimerService timerService = new TimerService();
    timerService.schedule();
}

觀察測試結(jié)果挪鹏,能夠發(fā)現(xiàn) TimerTask 配置的任務(wù)每隔 10s 被執(zhí)行了一次,執(zhí)行線程默認(rèn)都是 Timer-0 這個(gè)線程愉烙。

17:48:18.731 [Timer-0] INFO io.ostenant.springboot.sample.timer.TimerService - Schedule timerTask 1 times
17:48:28.730 [Timer-0] INFO io.ostenant.springboot.sample.timer.TimerService - Schedule timerTask 2 times
17:48:38.736 [Timer-0] INFO io.ostenant.springboot.sample.timer.TimerService - Schedule timerTask 3 times
17:48:48.738 [Timer-0] INFO io.ostenant.springboot.sample.timer.TimerService - Schedule timerTask 4 times
17:48:58.743 [Timer-0] INFO io.ostenant.springboot.sample.timer.TimerService - Schedule timerTask 5 times

配置ScheduledExecutorService任務(wù)

ScheduledExecutorService延時(shí)執(zhí)行 的線程池讨盒,對于 多線程 環(huán)境下的 定時(shí)任務(wù),推薦用 ScheduledExecutorService 代替 Timer 定時(shí)器步责。

創(chuàng)建一個(gè)線程數(shù)量為 4任務(wù)線程池返顺,同一時(shí)刻并向它提交 4 個(gè)定時(shí)任務(wù),用于測試延時(shí)任務(wù)的 并發(fā)處理蔓肯。執(zhí)行 ScheduledExecutorServicescheduleWithFixedDelay() 方法遂鹊,設(shè)置任務(wù)線程池的 初始任務(wù)延遲時(shí)間2 秒,并在上一次 執(zhí)行完畢時(shí)間點(diǎn) 之后 10 秒再執(zhí)行下一次任務(wù)蔗包。

public void scheduleWithFixedDelay() {
    ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4);
    for (int i = 0; i < 4; i++) {
        scheduledExecutor.scheduleWithFixedDelay(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(10 * 1000L);
            } catch (InterruptedException e) {
                LOGGER.error("Interrupted exception", e);
            }
            long count = counter.incrementAndGet();
            LOGGER.info("Schedule executor {} times with fixed delay", count);
        }, 2000L, 10 * 1000L, TimeUnit.MILLISECONDS);
    }
    LOGGER.info("Start to schedule");
}

測試結(jié)果如下秉扑,我們可以發(fā)現(xiàn)每隔 20 秒的時(shí)間間隔,就會(huì)有 4 個(gè)定時(shí)任務(wù)同時(shí)執(zhí)行调限。因?yàn)樵谌蝿?wù)線程池初始化時(shí)舟陆,我們同時(shí)向線程池提交了 4 個(gè)任務(wù),這 四個(gè)任務(wù) 會(huì)完全利用線程池中的 4 個(gè)線程進(jìn)行任務(wù)執(zhí)行耻矮。

20 秒是怎么來的秦躯?首先每個(gè)任務(wù)的 時(shí)間間隔 設(shè)置為 10 秒。其次因?yàn)椴捎玫氖?withFixedDelay 策略裆装,即當(dāng)前任務(wù)執(zhí)行的 結(jié)束時(shí)間踱承,作為下次延時(shí)任務(wù)的 開始計(jì)時(shí)節(jié)點(diǎn),并且每個(gè)任務(wù)在執(zhí)行過程中睡眠了 10 秒的時(shí)間哨免,累計(jì)起來就是 20 秒的時(shí)間茎活。

19:42:02.444 [main] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Start to schedule
19:42:14.449 [pool-1-thread-1] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 3 times with fixed delay
19:42:14.449 [pool-1-thread-2] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 1 times with fixed delay
19:42:14.449 [pool-1-thread-3] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 2 times with fixed delay
19:42:14.449 [pool-1-thread-4] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 4 times with fixed delay
19:42:34.458 [pool-1-thread-4] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 7 times with fixed delay
19:42:34.458 [pool-1-thread-3] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 5 times with fixed delay
19:42:34.458 [pool-1-thread-2] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 8 times with fixed delay
19:42:34.458 [pool-1-thread-1] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 6 times with fixed delay

創(chuàng)建一個(gè)線程數(shù)量為 4任務(wù)線程池,同一時(shí)刻并向它提交 4 個(gè)定時(shí)任務(wù)琢唾,用于測試延時(shí)任務(wù)的 并發(fā)處理载荔。每個(gè)任務(wù)分別執(zhí)行 ScheduledExecutorServicescheduleAtFixedRate() 方法,設(shè)置任務(wù)線程池的 初始任務(wù)延遲時(shí)間2 秒慧耍,并在上一次 開始執(zhí)行時(shí)間點(diǎn) 之后 10 秒再執(zhí)行下一次任務(wù)身辨。

public void scheduleAtFixedRate() {
    ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4);
    for (int i = 0; i < 4; i++) {
        scheduledExecutor.scheduleAtFixedRate(() -> {
            long count = counter.incrementAndGet();
            LOGGER.info("Schedule executor {} times at fixed rate", count);
        }, 2000L, 10 * 1000L, TimeUnit.MILLISECONDS);
    }
    LOGGER.info("Start to schedule");
}

測試結(jié)果如下,我們可以發(fā)現(xiàn)每隔 10 秒的時(shí)間間隔芍碧,就會(huì)有 4 個(gè)定時(shí)任務(wù)同時(shí)執(zhí)行煌珊,因?yàn)樵谌蝿?wù)線程池初始化時(shí),我們同時(shí)向線程池提交了 4 個(gè)任務(wù)泌豆,這 四個(gè)任務(wù) 會(huì)完全利用線程池中的 4 個(gè)線程進(jìn)行任務(wù)執(zhí)行定庵。

19:31:46.837 [main] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Start to schedule
19:31:48.840 [pool-1-thread-1] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 1 times at fixed rate
19:31:48.840 [pool-1-thread-3] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 3 times at fixed rate
19:31:48.840 [pool-1-thread-2] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 2 times at fixed rate
19:31:48.840 [pool-1-thread-4] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 4 times at fixed rate
19:31:58.839 [pool-1-thread-2] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 6 times at fixed rate
19:31:58.840 [pool-1-thread-4] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 8 times at fixed rate
19:31:58.839 [pool-1-thread-3] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 7 times at fixed rate
19:31:58.839 [pool-1-thread-1] INFO io.ostenant.springboot.sample.executor.ScheduledExecutorsService - Schedule executor 5 times at fixed rate

配置Spring Task任務(wù)

Spring 提供了 @Scheduled 注解來實(shí)現(xiàn) 定時(shí)任務(wù)@Scheduled 參數(shù)可以接受 兩種 定時(shí)的設(shè)置,一種是我們常用的 格林時(shí)間表達(dá)式 cron = "*/10 * * * * *"蔬浙,另一種是 fixedRate = 10 * 1000L猪落,兩種都表示每隔 10 秒執(zhí)行一次目標(biāo)任務(wù)。

參數(shù)說明:

  • @Scheduled(fixedRate = 10 * 1000L):上一次 開始執(zhí)行時(shí)間點(diǎn) 之后 10 秒再執(zhí)行畴博。
@Scheduled(fixedRate = 10 * 1000L)
public void scheduleAtFixedRate() throws Exception {
    long count = counter.incrementAndGet();
    LOGGER.info("Schedule executor {} times at fixed rate", count);
}
  • @Scheduled(fixedDelay = 10 * 1000L):上一次 執(zhí)行完畢時(shí)間點(diǎn) 之后 10 秒再執(zhí)行笨忌。
@Scheduled(fixedDelay = 10 * 1000L)
public void scheduleWithFixedDelay() throws Exception {
    try {
        TimeUnit.MILLISECONDS.sleep(10 * 1000L);
    } catch (InterruptedException e) {
        LOGGER.error("Interrupted exception", e);
    }
    long count = counter.incrementAndGet();
    LOGGER.info("Schedule executor {} times with fixed delay", count);
}
  • @Scheduled(initialDelay = 2000L, fixedRate = 10 * 1000L):第一次延遲 2 秒后執(zhí)行,之后按 fixedRate 的規(guī)則每 10 秒執(zhí)行一次俱病。
@Scheduled(initialDelay = 2000L, fixedDelay = 10 * 1000L)
public void scheduleWithinitialDelayAndFixedDelay() throws Exception {
    try {
        TimeUnit.MILLISECONDS.sleep(10 * 1000L);
    } catch (InterruptedException e) {
        LOGGER.error("Interrupted exception", e);
    }
    long count = counter.incrementAndGet();
    LOGGER.info("Schedule executor {} times with fixed delay", count);
}
  • @Scheduled(cron = "0/10 * * * * *"):根據(jù) cron 表達(dá)式定義官疲,每隔 10 秒執(zhí)行一次。
@Scheduled(cron = "0/10 * * * * *")
public void scheduleWithCronExpression() throws Exception {
    long count = counter.incrementAndGet();
    LOGGER.info("Schedule executor {} times with ", count);
}

完整的代碼如下:

SpringTaskService.java

@Component
public class SpringTaskService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SpringTaskService.class);
    private AtomicLong counter = new AtomicLong();

    @Scheduled(fixedDelay = 10 * 1000L)
    public void scheduleWithFixedDelay() throws Exception {
        try {
            TimeUnit.MILLISECONDS.sleep(10 * 1000L);
        } catch (InterruptedException e) {
            LOGGER.error("Interrupted exception", e);
        }
        long count = counter.incrementAndGet();
        LOGGER.info("Schedule executor {} times with fixed delay", count);
    }

    @Scheduled(initialDelay = 2000L, fixedDelay = 10 * 1000L)
    public void scheduleWithinitialDelayAndFixedDelay() throws Exception {
        try {
            TimeUnit.MILLISECONDS.sleep(10 * 1000L);
        } catch (InterruptedException e) {
            LOGGER.error("Interrupted exception", e);
        }
        long count = counter.incrementAndGet();
        LOGGER.info("Schedule executor {} times with fixed delay", count);
    }

    @Scheduled(fixedRate = 10 * 1000L)
    public void scheduleAtFixedRate() throws Exception {
        long count = counter.incrementAndGet();
        LOGGER.info("Schedule executor {} times at fixed rate", count);
    }

    @Scheduled(cron = "0/10 * * * * *")
    public void scheduleWithCronExpression() throws Exception {
        long count = counter.incrementAndGet();
        LOGGER.info("Schedule executor {} times with ", count);
    }
}

查看日志亮隙,任務(wù)每 20 秒的時(shí)間間隔執(zhí)行一次途凫。每次定時(shí)任務(wù)在上次 執(zhí)行完畢時(shí)間點(diǎn) 之后 10 秒再執(zhí)行,在任務(wù)中設(shè)置 睡眠時(shí)間10 秒溢吻。這里只驗(yàn)證了 @Scheduled(initialDelay = 2000L, fixedDelay = 10 * 1000L)维费。

2018-06-25 18:00:53.051  INFO 5190 --- [pool-1-thread-1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 1 times with fixed delay
2018-06-25 18:01:13.056  INFO 5190 --- [pool-1-thread-1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 2 times with fixed delay
2018-06-25 18:01:33.061  INFO 5190 --- [pool-1-thread-1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 3 times with fixed delay
2018-06-25 18:01:53.071  INFO 5190 --- [pool-1-thread-1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 4 times with fixed delay
2018-06-25 18:02:13.079  INFO 5190 --- [pool-1-thread-1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 5 times with fixed delay

配置任務(wù)線程池

上述配置都是基于 單線程 的任務(wù)調(diào)度,如何引入 多線程 提高 延時(shí)任務(wù)并發(fā)處理 能力促王?

Spring Boot 提供了一個(gè) SchedulingConfigurer 配置接口犀盟。我們通過 ScheduleConfig 配置文件實(shí)現(xiàn) ScheduleConfiguration 接口,并重寫 configureTasks() 方法硼砰,向 ScheduledTaskRegistrar 注冊一個(gè) ThreadPoolTaskScheduler 任務(wù)線程對象即可且蓬。

@Configuration
public class ScheduleConfiguration implements SchedulingConfigurer {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleConfiguration.class);

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(taskScheduler());
    }

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(4);
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        taskScheduler.setThreadNamePrefix("schedule");
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setErrorHandler(t -> LOGGER.error("Error occurs", t));
        return taskScheduler;
    }
}

啟動(dòng) Spring Boot 引用欣硼,上面 SpringTaskService 配置的 4 個(gè)定時(shí)任務(wù)會(huì)同時(shí)生效题翰。

2018-06-20 20:37:50.746  INFO 8142 --- [      schedule1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 1 times at fixed rate
2018-06-20 20:38:00.001  INFO 8142 --- [      schedule3] i.o.s.sample.spring.SpringTaskService    : Schedule executor 2 times with 
2018-06-20 20:38:00.751  INFO 8142 --- [      schedule1] i.o.s.sample.spring.SpringTaskService    : Schedule executor 3 times at fixed rate
2018-06-20 20:38:02.748  INFO 8142 --- [      schedule2] i.o.s.sample.spring.SpringTaskService    : Schedule executor 4 times with fixed delay
2018-06-20 20:38:10.005  INFO 8142 --- [      schedule4] i.o.s.sample.spring.SpringTaskService    : Schedule executor 5 times with 
2018-06-20 20:38:10.747  INFO 8142 --- [      schedule3] i.o.s.sample.spring.SpringTaskService    : Schedule executor 6 times at fixed rate
2018-06-20 20:38:20.002  INFO 8142 --- [      schedule2] i.o.s.sample.spring.SpringTaskService    : Schedule executor 7 times with 
2018-06-20 20:38:20.747  INFO 8142 --- [      schedule4] i.o.s.sample.spring.SpringTaskService    : Schedule executor 8 times at fixed rate

觀察日志,線程名前綴schedule诈胜,可以發(fā)現(xiàn) Spring Task@Scheduled 注解配置的 4 個(gè)任務(wù)豹障,分發(fā)給我們配置的 ThreadPoolTaskScheduler 中的 4 個(gè)線程并發(fā)執(zhí)行。

小結(jié)

本文介紹了基于單節(jié)點(diǎn)的定時(shí)任務(wù)調(diào)度及實(shí)現(xiàn)焦匈,包括 JDK 原生的 TimerScheduledExecutorService血公,以及 Spring 3.0 以后自帶的基于注解的 Spring Task 任務(wù)調(diào)度方式。除此之外缓熟,重點(diǎn)闡述了基于 固定延時(shí)累魔、固定頻率cron 表達(dá)式 的不同之處,并對 ScheduledExecutorServiceSpring Scheduler線程池并發(fā)處理 進(jìn)行了測試够滑。


歡迎關(guān)注技術(shù)公眾號: 零壹技術(shù)棧

零壹技術(shù)棧

本帳號將持續(xù)分享后端技術(shù)干貨垦写,包括虛擬機(jī)基礎(chǔ),多線程編程彰触,高性能框架梯投,異步、緩存和消息中間件,分布式和微服務(wù)分蓖,架構(gòu)學(xué)習(xí)和進(jìn)階等學(xué)習(xí)資料和文章尔艇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市么鹤,隨后出現(xiàn)的幾起案子终娃,更是在濱河造成了極大的恐慌,老刑警劉巖蒸甜,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尝抖,死亡現(xiàn)場離奇詭異,居然都是意外死亡迅皇,警方通過查閱死者的電腦和手機(jī)昧辽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來登颓,“玉大人僧叉,你說我怎么就攤上這事√杀颍” “怎么了簿姨?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長喇嘱。 經(jīng)常有香客問我茉贡,道長,這世上最難降的妖魔是什么者铜? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任腔丧,我火速辦了婚禮,結(jié)果婚禮上作烟,老公的妹妹穿的比我還像新娘愉粤。我一直安慰自己,他們只是感情好拿撩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布衣厘。 她就那樣靜靜地躺著,像睡著了一般压恒。 火紅的嫁衣襯著肌膚如雪影暴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天探赫,我揣著相機(jī)與錄音型宙,去河邊找鬼。 笑死期吓,一個(gè)胖子當(dāng)著我的面吹牛早歇,可吹牛的內(nèi)容都是我干的倾芝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼箭跳,長吁一口氣:“原來是場噩夢啊……” “哼晨另!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谱姓,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤借尿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后屉来,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體路翻,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年茄靠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茂契。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慨绳,死狀恐怖掉冶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脐雪,我是刑警寧澤厌小,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站战秋,受9級特大地震影響璧亚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脂信,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一癣蟋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吉嚣,春花似錦梢薪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甜攀。三九已至秋泄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間规阀,已是汗流浹背恒序。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谁撼,地道東北人歧胁。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喊巍。 傳聞我的和親對象是個(gè)殘疾皇子屠缭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)崭参,斷路器呵曹,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 博客原文 徒手翻譯spring framework 4.2.3官方文檔的第33章,若有翻譯不當(dāng)之處請指正何暮。 定時(shí)任...
    rabbitGYK閱讀 5,619評論 4 24
  • 定時(shí)任務(wù)實(shí)現(xiàn)的幾種方式: 1奄喂、Timer:這是java自帶的java.util.Timer類,這個(gè)類允許你調(diào)度一個(gè)...
    Steven_sunlu閱讀 82,899評論 1 18
  • title: springboot定時(shí)任務(wù)copyright: truecategories: springmvc...
    qinhej閱讀 1,126評論 0 0
  • 今天一早惶恐難安海洼,恍夢已時(shí)古時(shí)舊人跨新,權(quán)傾歸田。一日不時(shí)不知原由坏逢,登車上路玻蝌,窗換繩換,猶如舊時(shí)之景词疼,感慨笑之俯树。行若風(fēng)...
    涼皮君啊閱讀 478評論 0 0