一起來學SpringBoot | 第十六篇:定時任務詳解


SpringBoot 是為了簡化 Spring 應用的創建、運行、調試、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML配置,我們只需遵循規范,引入相關的依賴就可以輕易的搭建出一個 WEB 工程

在我們日常開發中,經常會遇到 數據定時增量同步定時發送郵件爬蟲定時抓取 的需求;這時我們可以采用定時任務的方式去進行工作…..

定時任務概述


定時任務:顧名思義就是在指定/特定的時間進行工作,比如我們的手機鬧鍾,它就是一種定時任務。

實現方式

Timer: JDK自帶的java.util.Timer;通過調度java.util.TimerTask的方式 讓程序按照某一個頻度執行,但不能在指定時間運行。 一般用的較少。

ScheduledExecutorService: JDK1.5新增的,位於java.util.concurrent包中;是基於線程池設計的定時任務類,每個調度任務都會被分配到線程池中,並發執行,互不影響。

Spring Task: Spring3.0 以后新增了task,一個輕量級的Quartz,功能夠用,用法簡單。

Quartz: 功能最為強大的調度器,可以讓程序在指定時間執行,也可以按照某一個頻度執行,它還可以動態開關,但是配置起來比較復雜。現如今開源社區中已經很多基於Quartz 實現的分布式定時任務項目xxl-jobelastic-job)。

Timer 方式


基於 Timer 實現的定時調度,基本就是手擼代碼,目前應用較少,不是很推薦

package com.lpf.chapter15.timer;

import java.time.LocalDateTime;
import java.util.Timer;
import java.util.TimerTask;

/** * 基於Timer實現的定時調度(不推薦,用該方式不如用 ScheduledExecutorService ) */
public class TimerDemo {

    public static void main(String[] args) {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("執行任務:" + LocalDateTime.now());
            }
        };
        Timer timer = new Timer();
        // timerTask:需要執行的任務
        // delay:延遲時間(以毫秒為單位)
        // period:間隔時間(以毫秒為單位)
        timer.schedule(timerTask, 5000, 3000);
    }
}

基於 ScheduledExecutorService


Timer很類似,但它的效果更好,多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中有一個因任務報錯沒有捕獲拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則可以規避這個問題

package com.lpf.chapter15.scheduled;

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/** * 基於 ScheduledExecutorService 方式,相對的比 Timer 要好 */
public class ScheduledExecutorServiceDemo {


    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
        // 參數:1、具體執行的任務 2、首次執行的延時時間
        // 3、任務執行間隔 4、間隔時間單位
        service.scheduleAtFixedRate(() -> System.out.println("執行任務A:" + LocalDateTime.now()), 0, 3, TimeUnit.SECONDS);
    }

}

Spring Task(本章關鍵)


導入依賴

pom.xml 中添加 spring-boot-starter-web 依賴即可,它包含了spring-context,定時任務相關的就屬於這個JAR下的org.springframework.scheduling包中

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
定時任務

@Scheduled 定時任務的核心

  • cron: cron表達式,根據表達式循環執行,與fixedRate屬性不同的是它是將時間進行了切割。(@Scheduled(cron = "0/5 * * * * *")任務將在5、10、15、20...這種情況下進行工作)
  • fixedRate: 每隔多久執行一次,無視工作時間(@Scheduled(fixedRate = 1000) 假設第一次工作時間為2018-05-29 16:58:28,工作時長為3秒,那么下次任務的時候就是2018-05-29 16:58:31
  • fixedDelay: 當前任務執行完畢后等待多久繼續下次任務(@Scheduled(fixedDelay = 3000) 假設第一次任務工作時間為2018-05-29 16:54:33,工作時長為5秒,那么下次任務的時間就是2018-05-29
    16:54:41
  • initialDelay: 第一次執行延遲時間,只是做延遲的設定,與fixedDelay關系密切,配合使用,相輔相成。

@Async 代表該任務可以進行異步工作,由原本的串行改為並行

package com.lpf.chapter15.task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/** * 基於 Spring 自帶的 */
@Component
public class SpringTaskDemo {

    private static final Logger log = LoggerFactory.getLogger(SpringTaskDemo.class);

    @Async
    @Scheduled(cron = "0/1 * * * * *")
    public void scheduled1() throws InterruptedException {
        Thread.sleep(3000);
        log.info("scheduled1 每1秒執行一次:{}", LocalDateTime.now());
    }

    @Scheduled(fixedRate = 1000)
    public void scheduled2() throws InterruptedException {
        Thread.sleep(3000);
        log.info("scheduled2 每1秒執行一次:{}", LocalDateTime.now());
    }

    @Scheduled(fixedDelay = 3000)
    public void scheduled3() throws InterruptedException {
        Thread.sleep(5000);
        log.info("scheduled3 上次執行完畢后隔3秒繼續執行:{}", LocalDateTime.now());
    }

}

cron表達式在線生成: http://www.pdtools.net/tools/becron.jsp

主函數

@EnableScheduling 注解表示開啟對@Scheduled注解的解析;同時new ThreadPoolTaskScheduler()也是相當的關鍵,通過閱讀源碼可以發現默認情況下的 private volatile int poolSize = 1;這就導致了多個任務的情況下容易出現競爭情況(多個任務的情況下,如果第一個任務沒執行完畢,后續的任務將會進入等待狀態)。

@EnableAsync 注解表示開啟@Async注解的解析;作用就是將串行化的任務給並行化了。(@Scheduled(cron = "0/1 * * * * *")假設第一次工作時間為2018-05-29 17:30:55,工作周期為3秒;如果不加@Async那么下一次工作時間就是2018-05-29 17:30:59;如果加了@Async 下一次工作時間就是2018-05-29 17:30:56

package com.lpf.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class Chapter15Application {

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

    /** * 很關鍵:默認情況下 TaskScheduler 的 poolSize = 1 * * @return 線程池 */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        return taskScheduler;
    }
}
測試

完成准備事項后,啟動Chapter15Application,觀察日志信息如下

2018-06-03 21:34:23.363  INFO 2652 --- [taskScheduler-1] com.lpf.chapter15.task.SpringTaskDemo    : scheduled2 每1秒執行一次:2018-06-03T21:34:23.363
2018-06-03 21:34:24.006  INFO 2652 --- [taskScheduler-3] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:24.006
2018-06-03 21:34:25.001  INFO 2652 --- [taskScheduler-5] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:25.001
2018-06-03 21:34:25.353  INFO 2652 --- [taskScheduler-2] com.lpf.chapter15.task.SpringTaskDemo    : scheduled3 上次執行完畢后隔3秒繼續執行:2018-06-03T21:34:25.353
2018-06-03 21:34:26.003  INFO 2652 --- [taskScheduler-6] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:26.003
2018-06-03 21:34:26.364  INFO 2652 --- [taskScheduler-7] com.lpf.chapter15.task.SpringTaskDemo    : scheduled2 每1秒執行一次:2018-06-03T21:34:26.364
2018-06-03 21:34:27.001  INFO 2652 --- [taskScheduler-4] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:27.001
2018-06-03 21:34:28.002  INFO 2652 --- [taskScheduler-1] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:28.002
2018-06-03 21:34:29.002  INFO 2652 --- [taskScheduler-3] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:29.002
2018-06-03 21:34:29.364  INFO 2652 --- [taskScheduler-7] com.lpf.chapter15.task.SpringTaskDemo    : scheduled2 每1秒執行一次:2018-06-03T21:34:29.364
2018-06-03 21:34:30.002  INFO 2652 --- [askScheduler-10] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:30.002
2018-06-03 21:34:31.003  INFO 2652 --- [taskScheduler-9] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:31.003
2018-06-03 21:34:32.001  INFO 2652 --- [taskScheduler-4] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:32.001
2018-06-03 21:34:32.365  INFO 2652 --- [taskScheduler-7] com.lpf.chapter15.task.SpringTaskDemo    : scheduled2 每1秒執行一次:2018-06-03T21:34:32.365
2018-06-03 21:34:33.002  INFO 2652 --- [taskScheduler-6] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:33.002
2018-06-03 21:34:33.355  INFO 2652 --- [taskScheduler-2] com.lpf.chapter15.task.SpringTaskDemo    : scheduled3 上次執行完畢后隔3秒繼續執行:2018-06-03T21:34:33.355
2018-06-03 21:34:34.001  INFO 2652 --- [taskScheduler-1] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:34.001
2018-06-03 21:34:35.001  INFO 2652 --- [taskScheduler-3] com.lpf.chapter15.task.SpringTaskDemo    : scheduled1 每1秒執行一次:2018-06-03T21:34:35.001

總結


目前很多大佬都寫過關於 SpringBoot 的教程了,如有雷同,請多多包涵,本教程基於最新的 spring-boot-starter-parent:2.0.2.RELEASE編寫,包括新版本的特性都會一起介紹…



源碼地址:一起來學SpringBoot | 第十六篇:定時任務詳解






跟着原作者的步驟一點一點敲過來,所以有些地方強迫症式的做了修改,請勿見怪。
來源:http://blog.battcn.com/2018/05/29/springboot/v2-other-scheduling/


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com