常見實現方案
- @Scheduled注解:基于注解
- Timer().schedule創建任務:基于封裝類
Timer
- 線程:使用線程直接執行任務即可,可以與thread、線程池、ScheduleTask等配合使用
- quartz配置定時器:基于
spring
的quartz
框架
@Scheduled注解實現定時器
使用注解標記需要定時執行的方法,并設置執行時間,便可使其在指定的時間執行指定方法
步驟:
- 使用注解
@Scheduled
標記目標方法,參數為執行時間- 使用注解
@EnableScheduling
標記目標方法所在的類,或者直接標記項目啟動類
@Scheduled(fixedDelay = 5000):方法執行完成后等待5秒再次執行
@Scheduled(fixedRate = 5000):方法每隔5秒執行一次
@Scheduled(initialDelay=1000, fixedRate=5000):延遲1秒后執行第一次,之后每隔5秒執行一次
fixedDelayString、fixedRateString、initialDelayString:與上訴三種作用一直,但參數為字符串類型,因而可以使用占位符,形如@Scheduled(fixedDelayString = "${time.fixedDelay}")
@Scheduled(cron = "0 0,30 0,8 ? * ? "):方法在每天的8點30分0秒執行,參數為字符串類型,那么同理也可使用占位符
cron 該參數接收一個
cron表達式
,cron表達式
是一個字符串,字符串以5或6個空格隔開,分開共6或7個域,[年]不是必須的域,可以省略[年],則一共6個域
[秒] [分] [小時] [日] [月] [周] [年]
序號 | 說明 | 必填 | 允許填寫的值 | 允許的通配符 |
---|---|---|---|---|
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 | , - * / |
通配符說明:
*
表示所有值。 例如:在分的字段上設置 *,表示每一分鐘都會觸發。?
表示不指定值。使用的場景為不需要關心當前設置這個字段的值。例如:要在每月的10號觸發一個操作,但不關心是周幾,所以需要周位置的那個字段設置為”?” 具體設置為 0 0 0 10 * ?-
表示區間。例如 在小時上設置 “10-12”,表示 10,11,12點都會觸發。,
表示指定多個值,例如在周字段上設置 “MON,WED,FRI” 表示周一,周三和周五觸發/
用于遞增觸發。如在秒上面設置”5/15” 表示從5秒開始,每增15秒觸發(5,20,35,50)。 在日字段上設置’1/3’所示每月1號開始,每隔三天觸發一次。L
表示最后的意思。在日字段設置上,表示當月的最后一天(依據當前月份,如果是二月還會依據是否是潤年[leap]), 在周字段上表示星期六,相當于”7”或”SAT”。如果在”L”前加上數字,則表示該數據的最后一個。例如在周字段上設置”6L”這樣的格式,則表示“本月最后一個星期五”W
表示離指定日期的最近那個工作日(周一至周五). 例如在日字段上置”15W”,表示離每月15號最近的那個工作日觸發。如果15號正好是周六,則找最近的周五(14號)觸發, 如果15號是周未,則找最近的下周一(16號)觸發.如果15號正好在工作日(周一至周五),則就在該天觸發。如果指定格式為 “1W”,它則表示每月1號往后最近的工作日觸發。如果1號正是周六,則將在3號下周一觸發。(注,”W”前只能設置具體的數字,不允許區間”-“)。#
序號(表示每月的第幾個周幾),例如在周字段上設置”6#3”表示在每月的第三個周六.注意如果指定”#5”,正好第五周沒有周六,則不會觸發該配置(用在母親節和父親節再合適不過了) ;小提示:’L’和 ‘W’可以一組合使用。如果在日字段上設置”LW”,則表示在本月的最后一個工作日觸發;周字段的設置,若使用英文字母是不區分大小寫的,即MON與mon相同。
示例
每隔5秒執行一次:*/5 * * * * ?
每隔1分鐘執行一次:0 */1 * * * ?
每天23點執行一次:0 0 23 * * ?
每天凌晨1點執行一次:0 0 1 * * ?
每月1號凌晨1點執行一次:0 0 1 1 * ?
每月最后一天23點執行一次:0 0 23 L * ?
每周星期六凌晨1點實行一次:0 0 1 ? * L
在26分、29分、33分執行一次:0 26,29,33 * * * ?
每天的0點、13點、18點、21點都執行一次:0 0 0,13,18,21 * * ?
使用占位符
另外,cron
屬性接收的cron表達式
支持占位符
配置文件:
time:cron: */5 * * * * *interval: 5
每5秒執行一次:
@Scheduled(cron="${time.cron}")void testPlaceholder1() {System.out.println("Execute at " + System.currentTimeMillis());}@Scheduled(cron="*/${time.interval} * * * * *")void testPlaceholder2() {System.out.println("Execute at " + System.currentTimeMillis());}
第一次等待10秒,之后每3秒一次
@Component
@EnableScheduling
public class ScheduleTest {private int count = 0;/*** 第一次等待10秒,之后每3秒鐘執行一次*/@Scheduled(initialDelay = 10000, fixedRate = 3000)public void test1() {System.out.println(count + ":" + (new Date()).toString());count++;}}
?
Timer().schedule實現定時器
核心包括Timer和TimerTask,均為jkd自帶的工具類
TimerTask實際上就是一個Runnable而已,繼承Runnable并添加了幾個自定義的參數和方法
Timer字面意思即定時器,為jkd自帶的工具類,提供定時執行任務的相關功能
實際上包括三個類:
Timer:即定時器主類,負責管理所有的定時任務,每個Timer擁有一個私有的TaskQueue和TimerThread,
TaskQueue:即任務隊列,Timer生產任務,然后推到TaskQueue里存放,等待處理,被處理掉的任務即被移除掉
TaskQueue實質上只有一個長度為128的數組用于存儲TimerTask、一個int型變量size表示隊列長度、以及對這兩個數據的增刪改查
TimerThread:即定時器線程,線程會共享TaskQueue里面的數據,TimerThread會對TaskQueue里的任務進行消耗
TimerThread實際上就是一個Thread線程,會不停的監聽TaskQueue,如果隊列里面有任務,那么就執行第一個,并將其刪除(先刪除再執行)
流程分析
Timer生產任務(實際上是從外部接收到任務),并將任務推到TaskQueue里面存放,并喚醒TaskQueue線程(queue.notify())
TimerThread監聽TaskQueue,若里面有任務則將其執行并移除隊里,若沒有任務則讓隊列等待(queue.wait())
構造
public Timer(String name, boolean isDaemon)
- name:即線程名,用于區分不同的線程,缺省的時候默認使用
"Timer-" + serialNumber()
生成唯一線程名- isDaemon:是否是守護線程,缺省的時候默認為否
方法
schedule(TimerTask task, long delay):指定任務task,在delay毫秒延遲后執行
schedule(TimerTask task, Date time):指定任務task,在time時間點執行一次
schedule(TimerTask task, long delay, long period):指定任務task,延遲delay毫秒后執行第一次,并在之后每隔period毫秒執行一次
schedule(TimerTask task, Date firstTime, long period):指定任務task,在firstTime的時候執行第一次,之后每隔period毫秒執行一次
scheduleAtFixedRate(TimerTask task, long delay, long period):作用與schedule一致
scheduleAtFixedRate(TimerTask task, Date firstTime, long period):作用與schedule一致實際上最后都會使用sched(TimerTask task, long time, long period),即指定任務task,在time執行第一次,之后每隔period毫秒執行一次
schedule使用系統時間計算下一次,即System.currentTimeMillis()+period
而scheduleAtFixedRate使用本次預計時間計算下一次,即time + period
對于耗時任務,兩者區別較大,請按需求選擇,瞬時任務無區別
取消任務方法:cancel(),會將任務隊列清空,并堵塞線程,且不再能夠接受任務(接受時報錯),并不會銷毀本身的實例和其內部的線程
凈化方法:purge(),凈化會將隊列里所有被取消的任務移除,對剩余任務進行堆排序,并返回移除任務的數量
@Component
public class TimerTest {private Integer count = 0;public TimerTest() {testTimer();}public void testTimer() {new Timer().schedule(new TimerTask() {@Overridepublic void run() {try {//do SomethingSystem.out.println(new Date().toString() + ": " + count);count++;} catch (Exception e) {e.printStackTrace();}}}, 0, 1000);}
}
線程實現定時器
使用thread + runnable
public class ThreadTest {private Integer count = 0;public ThreadTest() {test1();}public void test1() {new Thread(() -> {while (count < 10) {System.out.println(new Date().toString() + ": " + count);count++;try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}
使用線程池 + runnable
public class ThreadTest {private static final ExecutorService threadPool = Executors.newFixedThreadPool(5);// 線程池private Integer count = 0;public ThreadTest() {test2();}public void test2() {threadPool.execute(() -> {while (count < 10) {System.out.println(new Date().toString() + ": " + count);count++;try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});}
}
使用org.springframework.scheduling.TaskScheduler+ runnable
設置觸發頻率為3000毫秒
@Component
public class ThreadTest {private Integer count = 0;private final TaskScheduler taskScheduler;public ThreadTest(TaskScheduler taskScheduler) {this.taskScheduler = taskScheduler;test3();}public void test3() {taskScheduler.scheduleAtFixedRate(() -> {System.out.println(new Date().toString() + ": " + count);count++;}, 3000);}
}
設置觸發時間為每天凌晨1點
@Component
public class ThreadTest {private Integer count = 0;private final TaskScheduler taskScheduler;public ThreadTest(TaskScheduler taskScheduler) {this.taskScheduler = taskScheduler;test4();}public void test4() {taskScheduler.schedule(() -> {System.out.println(new Date().toString() + ": " + count);count++;}, new CronTrigger("0 0 1 * * ?"));}
}