SpringBoot定時任務和異步操做

SpringBoot定時任務和異步操做

平常求贊,感謝老闆。

歡迎關注公衆號:實際上是白羊。乾貨持續更新中......java

1、定時任務

在作業務時總會有這樣的場景:在特定時間去執行某些邏輯。這其實就是定時任務的應用場景,好比:須要每個月一日給用戶發上月數據總結等場景。spring

1.技術

實現定時任務的技術不少多線程

  • Timer:JDK自帶的java.util.Timer其實更相似於定時器,可實現延遲執行和按照必定頻率執行,也能夠指定某個時間執行,使用較少
  • ScheduledExecutorService:也是JDK自帶的,是基於線程池設計的定時任務類,根據Executors建立時的線程數量去執行具體任務(多個線程數量就是每一個人物分配一個線程)
  • Spring Task:即今天要介紹了主角,是Spring自帶的,固然這裏經過Springboot使用。
  • Quartz:開源的調度框架,功能更增強大

2.註解使用

1)@EnableScheduling

開啓定時任務,可標註在啓動類或者任何配置類上(能掃描到對象上均可以)併發

2)@Scheduled

配置具體的任務執行規則,可標註在能被掃描的類的方法上,屬性有:app

  • fixedRate:上一個任務開始時間和下一個任務開始時間的時間間隔(毫秒)
  • fixedDelay:上一個任務的結束時間和下一個任務的開始時間的時間間隔(毫秒)
  • initialDelay:第一次執行延遲執行的時間(毫秒)
  • cron:經過cron表達式來配置執行時機

3.cron表達式

定時任務的場景:延遲執行、必定頻率執行、指定時間執行框架

這裏的cron表達式即爲了描述任務執行的時間規則。cron由6-7個元素組成,他們之間使用空格來分割,以此表明:異步

  • 秒:0~59
  • 分:0~59
  • 時:0~23
  • 日:1~31(具體月的最後一天也多是30)
  • 月:1~12
  • 星期:1~7(注意1爲週日)
  • 年:1970~2099

除了上面的數字元素值還有下面幾個特殊的元素值:spring-boot

  • *:表示任意一個值都會觸發,七個元素位置中均可以出現,如在分鐘出現即表示每分鐘都回去執行
  • ?:和*相似,但只會出如今日和星期(這兩個位置只能有一個是具備意義的描述,如:要麼是每月的3號要麼是每月的每一個星期的週三),當其中一個配置了有意義的值,另外一個寫?
  • -:表示範圍(至),七個元素位置中均可以出現,如分鐘裏出現1-7則表示1分鐘到7分鐘每分鐘執行一次
  • /:表示從開始時間每隔多長時間執行一次,七個元素位置中均可以出現,如分鐘裏出現1/7則表示1分觸發一次1+7=8分觸發一次
  • ,:表示枚舉值,七個元素位置中均可以出現,如分鐘裏出現1,7則表示1分和7分各執行一次
  • L:表示最後,只會出如今日和星期位置,日表示最後一天,星期則會搭配數字如5L表示最後一個週四
  • W:表示有效工做日(週一到週五),只會出如今日期裏,前面會搭配數字,如5W:若是5號是週六那麼執行時間爲週五即4號;若是5號是週日那麼執行時間爲下週一即6號;(即系統會推薦離今天最近的一個工做日,但注意不會進行跨越查找:若是是31W且31號是週日,那麼會在29號執行)
  • LW:連用表示最後一個工做日,只會出如今日期裏
  • 「#」:(先後要加數字)表示第幾個星期幾,只會出如今星期裏,如4#2 即表示第二個週三(前面表示周幾,後面表示第幾個)

瞭解了上面的表達式規則就能夠寫出知足條件的cron表達式了,建議多設計幾個場景練習下ui

4.單線程和多線程執行

若是就按上面的配置寫好任務A(fixedRate=2000)和任務B(fixedRate=3000)直接啓動執行的話,會存在如下問題:this

  • 若是A1任務執行時間超過2s那麼原定於A1任務開始時2s後執行的任務A2就不能按時執行
  • 任務A和任務B要交替執行

上面的問題都不能讓咱們的任務按照各自規定的執行計劃去執行,歸根到底仍是全部的任務都在一個線程裏進行,因此作不到異步的併發執行,那這是就能夠經過多線程來實現各個任務之間異步的併發執行

這裏咱們可使用Spring的@Async註解來實現異步

2、異步

SpringBoot一樣支持@Async來實現異步(增長了自動配置可直接使用)

1.註解

1)@EnableAsync

標註在啓動類或配置類上,表是開啓異步

2)@Async

可標註在類上(能被spring容器掃描到的類上)或方法上

標註在類上則這個類裏的方法都被表示爲異步

2.異步方法兩種返回值

  1. 不須要返回值:void
  2. 須要返回值:

    @Async  
        public Future<String> doTaskOne() throws Exception {
            return new AsyncResult<>("任務一完成");  
        }

3.自動配置

使用@Async來實現異步,其底層仍是使用了多線程來進行實現的,那麼這個多線程或者線程池是在哪裏設置或配置的呢?咱們都直到SpringBoot加入了大量的自動配置,咱們在spring-boot-autoconfigure包下面能夠找到task包下的TaskExecutionAutoConfiguration類中:

@Lazy
@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
            AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
@ConditionalOnMissingBean(Executor.class)
//這個註解的意思:當在容器中沒有發現Executor這個類則會加載這個bean,能夠理解爲此處爲默認缺省對象
//返回的是一個spring爲咱們提供的線程池
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }

找到TaskExecutorBuilder:

@Bean
    @ConditionalOnMissingBean
    public TaskExecutorBuilder taskExecutorBuilder() {
        TaskExecutionProperties.Pool pool = this.properties.getPool();
        TaskExecutorBuilder builder = new TaskExecutorBuilder();
        builder = builder.queueCapacity(pool.getQueueCapacity());
        builder = builder.corePoolSize(pool.getCoreSize());
        builder = builder.maxPoolSize(pool.getMaxSize());
        builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
        builder = builder.keepAlive(pool.getKeepAlive());
        builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
        builder = builder.customizers(this.taskExecutorCustomizers.orderedStream()::iterator);
        builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
        return builder;
    }

這裏的properties是在TaskExecutionProperties加載進來的,默認的參數:

private int queueCapacity = Integer.MAX_VALUE;
private int coreSize = 8; 
private int maxSize = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeout = true;

根據下面可知,也能夠從yml/properties文件中自定義配置

@ConfigurationProperties("spring.task.execution")

除了上面的方法咱們還能夠定義本身的Bean來替換默認缺省Bean,根據@ConditionalOnMissingBean(Executor.class)可知,咱們只須要在配置類里加上:

@Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(核心線程數量);
        executor.setMaxPoolSize(最大線程數量);
        executor.setQueueCapacity(任務隊列大小);
        executor.initialize();
        return executor;
    }

3、最後

根據上面的介紹,要實現異步的定時任務就能夠展開,任務方法上面加上@Async來實現

點個贊啊親

若是你認爲本文對你有幫助,能夠「在看/轉發/贊/star」,多謝

若是你還發現了更好或不一樣的想法,還能夠在留言區一塊兒探討下


歡迎關注公衆號:「實際上是白羊」乾貨持續更新中......

相關文章
相關標籤/搜索