Spring自帶定時任務@Scheduled注解實例講解
1. cron表達式生成器
cron表達式生成器:https://cron.qqe2.com/
2. 簡單定時任務代碼示例:每隔兩秒打印一次字符
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service @EnableScheduling public class ScheduleDemo1 { @Scheduled(cron = "*/2 * * * * ?") public static void test() { // 十六進制轉換為字符 System.out.print((char) Integer.parseInt("5fc3", 16)); System.out.print((char) Integer.parseInt("6d41", 16)); System.out.print((char) Integer.parseInt("65f6", 16)); System.out.print((char) Integer.parseInt("95f4", 16)); System.out.println(); } }
輸出:
3. @Scheduled注解的參數
3.1 cron
參數接收一個cron表達式,cron表達式是一個以空格為間隔符來區(qū)分不同域的字符串,總共有6個或7個域。cron表達式從左到右每個域分別標識的[秒] [分] [小時] [日] [月] [周] [年],其中[年]不是必選的域可以省略。
序號 | 域 | 必填 | 值的范圍 | 允許的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 | , - * / |
3 | 時 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 / JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | 1970-2099 | , - * / |
通配符說明:
- * 表示所有值,例如:在時的字段上設置 *,表示每一個小時都會觸發(fā)。
- ? 表示不指定值,即當前使用的場景為不需要關心這個字段設置的值。例如:要在每月的10號觸發(fā)一個操作,但不關心是周幾,所以需要周位置的那個字段設置為“?”, 具體設置為 0 0 0 10 * ? 。
- - 表示區(qū)間,例如:在小時上設置 “10-12”,表示 10,11,12點都會觸發(fā)。
- , 表示指定多個值,例如在周字段上設置 “MON,WED,FRI” 表示周一,周三和周五觸發(fā)。
- / 用于遞增觸發(fā),如在秒上面設置“5/15” 表示從5秒開始,每隔15秒觸發(fā)(5,20,35,50)。在日字段上設置‘1/3’所示每月1號開始,每隔三天觸發(fā)一次
- L 表示最后的意思,在日字段設置上,表示當月的最后一天(依據當前月份,如果是二月還會依據是否是閏年), 在周字段上表示星期六,相當于“7”或“SAT”。如果在“L”前加上數字,則表示該數據的最后一個。例如在周字段上設置“6L”這樣的格式,則表示“本月最后一個星期五”。
- W表示離指定日期的最近那個工作日(周一至周五)。例如在日字段上置“15W”,表示離每月15號最近的那個工作日觸發(fā)。如果15號正好是周六,則找最近的周五(14號)觸發(fā), 如果15號是周未,則找最近的下周一(16號)觸發(fā)。如果15號正好在工作日(周一至周五),則就在該天觸發(fā)。如果指定格式為 “1W”,它則表示每月1號往后最近的工作日觸發(fā)。如果1號正是周六,則將在3號下周一觸發(fā)。(注,“W”前只能設置具體的數字,不允許區(qū)間“-”)。
- #序號(表示每月的第幾個周幾),例如在周字段上設置“6#3”表示在每月的第三個周六。注意如果指定“#5”,正好第五周沒有周六,則不會觸發(fā)該配置;小提示:‘L’和 ‘W’可以一組合使用。如果在日字段上設置“LW”,則表示在本月的最后一個工作日觸發(fā);周字段的設置,若使用英文字母是不區(qū)分大小寫的,即MON與mon相同。
示例:
- 每隔5秒執(zhí)行一次:*/5 * * * * ?
- 每隔1分鐘執(zhí)行一次:0 */1 * * * ?
- 每天23點執(zhí)行一次:0 0 23 * * ?
- 每天凌晨1點執(zhí)行一次:0 0 1 * * ?
- 每月1號凌晨1點執(zhí)行一次:0 0 1 1 * ?
3.2 fixedDelay
上一次執(zhí)行完成后延遲多久執(zhí)行下一次,以上一次任務執(zhí)行的完成時間開始延遲,如:
@Scheduled(fixedDelay = 5000) //上一次執(zhí)行完成后延遲5s再執(zhí)行
3.3 fixedRate
固定延遲多久執(zhí)行下一次任務,不依賴于上一次任務執(zhí)行成功的時間,如:
@Scheduled(fixedRate= 5000) //上一次執(zhí)行后延遲5s就開始執(zhí)行
3.4 initialDelay
啟動后延遲多久后執(zhí)行第一次,可根據場景搭配fixedRate或fixedDelay實現(xiàn)定時調度,如:
@Scheduled(initialDelay = 5000,fixedRate= 300000) //啟動后延遲5s執(zhí)行,之后每次執(zhí)行時間間隔5min
3.5 fixedDelayString、fixedRateString、initialDelayString等是String類型,支持占位符
如:@Scheduled(fixedDelayString = “${task.fixed-delay}”)
3.6 timeUnit
時間單位,默認毫秒
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
4. 問題:定時器的任務默認是按照順序執(zhí)行的,可能導致一些任務無法執(zhí)行
我創(chuàng)建定時器執(zhí)行任務目的是為了讓它多線程執(zhí)行任務,但是后來才發(fā)現(xiàn),@Scheduled注解的方法默認是按照順序執(zhí)行的,這會導致當一個任務掛死的情況下,其它任務都在等待,無法執(zhí)行。
@Scheduled注解加載的過程,以及它是如何執(zhí)行的:
4.1 ScheduledAnnotationBeanPostProcessor類處理器解析帶有@Scheduled注解的方法
4.2 processScheduled方法處理@Scheduled注解后面的參數,并將其添加到任務列表中
4.3 執(zhí)行任務。
ScheduledTaskRegistrar類為Spring容器的定時任務注冊中心。Spring容器通過線程處理注冊的定時任務
首先,調用scheduleCronTask初始化定時任務。
然后,在ThreadPoolTaskScheduler類中,會對線程池進行初始化,線程池的核心線程數量為1,
private volatile int poolSize = 1;
阻塞隊列為DelayedWorkQueue。
因此,原因就找到了,當有多個方法使用@Scheduled注解時,就會創(chuàng)建多個定時任務到任務列表中,當其中一個任務沒執(zhí)行完時,其它任務在阻塞隊列當中等待,因此,所有的任務都是按照順序執(zhí)行的,只不過由于任務執(zhí)行的速度相當快,讓我們感覺任務都是多線程執(zhí)行的。
下面舉例來驗證一下,將上述的某個定時任務添加睡眠時間,觀察另一個定時任務是否輸出。
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Slf4j @EnableScheduling @Component public class ScheduleDemo2 { private static final ThreadLocal<Integer> threadLocalA = new ThreadLocal<>(); @Scheduled(cron = "0/2 * * * * ?") public void taskA() { try { log.info("執(zhí)行了ScheduleTask類中的taskA方法"); Thread.sleep(TimeUnit.SECONDS.toMillis(10)); } catch (InterruptedException e) { e.printStackTrace(); } } @Scheduled(cron = "0/1 * * * * ?") public void taskB() { int num = threadLocalA.get() == null ? 0 : threadLocalA.get(); log.info("taskB方法執(zhí)行次數:{}", ++num); threadLocalA.set(num); } }
輸出:可以觀察到兩個定時任務不是同時執(zhí)行的,是按順序執(zhí)行的
想要避免順序執(zhí)行,進行并發(fā),就要配置定時任務線程池:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledThreadPoolExecutor; @Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(getExecutor()); } @Bean public Executor getExecutor(){ return new ScheduledThreadPoolExecutor(5); } }
輸出:可以觀察到兩個定時任務不是順序執(zhí)行了,從出現(xiàn)次數的亂序這種多線程問題也可以看出是并發(fā)執(zhí)行了
從輸出結果我們可以看到,即使testA休眠,但是testB仍然正常執(zhí)行,并且其還復用了其它線程,導致執(zhí)行次數發(fā)生了變化。
5. 問題:當系統(tǒng)時間發(fā)生改變時,@Scheduled注解失效
另外一種情況就是在配置完線程池之后,當你手動修改服務器時間時,目前我做的測試就是服務器時間調前,則會導致注解失效,而服務器時間調后,則不會影響注解的作用。
原因:
JVM啟動之后會記錄當前系統(tǒng)時間,然后JVM根據CPU ticks自己來算時間,此時獲取的是定時任務的基準時間。如果此時將系統(tǒng)時間進行了修改,當Spring將之前獲取的基準時間與當下獲取的系統(tǒng)時間進行比對不一致,就會造成Spring內部定時任務失效。因為此時系統(tǒng)時間發(fā)生變化了,不會觸發(fā)定時任務。
解決辦法:
重啟項目
不使用@Scheduled注解,改成ScheduledThreadPoolExecutor進行替代,部分代碼:
實際項目中一般使用xxl-job、Quartz等框架,@Scheduled注解會使用的話也是定時更新一些變量的值,大量的定時任務還是使用專門的定時任務框架實現(xiàn)
參考資料:
到此這篇關于Spring自帶定時任務@Scheduled注解的文章就介紹到這了,更多相關Spring定時任務@Scheduled注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- Spring定時任務@scheduled多線程使用@Async注解示例
- Spring定時任務@Scheduled注解(cron表達式fixedRate?fixedDelay)
- Spring中的@Scheduled定時任務注解詳解
- SpringBoot中@Scheduled()注解以及cron表達式詳解
- Spring 定時任務@Scheduled 注解中的 Cron 表達式詳解
- SpringBoot中定時任務@Scheduled注解的使用解讀
- spring-boot通過@Scheduled配置定時任務及定時任務@Scheduled注解的方法
- 詳解在Spring3中使用注解(@Scheduled)創(chuàng)建計劃任務
- spring @Scheduled定時任務注解使用方法及注意事項小結
相關文章
基于bufferedreader的read()與readline()讀取出錯原因及解決
這篇文章主要介紹了bufferedreader的read()與readline()讀取出錯原因及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12SpringCloud之分布式配置中心Spring Cloud Config高可用配置實例代碼
這篇文章主要介紹了SpringCloud之分布式配置中心Spring Cloud Config高可用配置實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04IDEA SpringBoot:Cannot resolve configuration&
這篇文章主要介紹了IDEA SpringBoot:Cannot resolve configuration property配置文件問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07SpringBoot Maven打包失敗報:class lombok.javac.apt.Lombo
最新項目部署的時候,出現(xiàn)了一個maven打包失敗的問題,報:class lombok.javac.apt.LombokProcessor錯誤,所以本文給大家介紹了如何解決SpringBoot Maven 打包失敗:class lombok.javac.apt.LombokProcessor 錯誤,需要的朋友可以參考下2023-12-12springboot2中session超時,退到登錄頁面方式
這篇文章主要介紹了springboot2中session超時,退到登錄頁面方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01詳談Array和ArrayList的區(qū)別與聯(lián)系
下面小編就為大家?guī)硪黄斦凙rray和ArrayList的區(qū)別與聯(lián)系。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06