在工作中遇到一個需求,需要定時自動執行某項功能,這就需要用到定時任務了。
定時任務排程:基于給定的時間點,給定的時間間隔或者給定的執行次數自動執行任務。
定時任務排程的幾種實作方式:
Timer:
Timer由JDK自帶,不需要引入多餘的jar。Java自帶的
java.util.Timer
類,這個類允許你排程一個
java.util.TimerTask
任務。Timer隻有一個背景線程執行任務,使用這種方式可以讓你的程式按照某一個頻度執行,但不能在指定時間運作。一般用的較少。
Quartz:
使用Quartz,這是一個功能比較強大的的排程器,可以讓你的程式在指定時間執行,也可以按照某一個頻度執行,配置起來稍顯複雜,Quartz擁有背景執行線程池能夠使用多個線程。
Spring Task:
Spring3.0以後自帶的task,可以将它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。
Timer代碼實作
首先寫個需要定時實作的邏輯,這裡是簡單輸出格式化後的時間
public class MyTimerTask extends TimerTask{
@Override
public void run() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(df.format(System.currentTimeMillis()));
}
}
然後寫定時任務排程類
public class MyTimer {
public static void main(String[] args){
//建立timer執行個體
Timer timer=new Timer();
//建立一個MyTimerTask執行個體
MyTimerTask myTimerTask=new MyTimerTask();
//通過timer定時調用myTimerTask中的run方法,因為 TimerTask 類實作了 Runnable 接口。
timer.schedule(myTimerTask,10*1000,2*1000);
}
}
QQ截圖20180609173715.png
這裡的重點是Timer中schedule()方法的三個參數
第一個參數:是 就是我們剛剛寫的
MyTimerTask
類,這裡需要繼承
TimerTask
,并實作
run()
方法,因為
TimerTask
類實作了
Runnable
接口。
第二個參數:是程式啟動後,timer定時器第一次調用run方法的時間,0表示不指時間,立刻調用。(10*1000表示10秒,因為參數機關是毫秒)
第三個參數:是指第一次調用之後,從第二次開始每隔多長的時間調用一次 run() 方法。機關同樣是毫秒。
拓展:
QQ圖檔20180609191051.png
其實Timer中schedule()方法一共有四種用法,剛剛隻是其中一種
schedule(task, time):time為
Date
類型,在指定時間執行一次。
schedule(task, firstTime, period):firstTime為
Date
類型,從
firstTime
時刻開始,每隔
period
毫秒執行一次。
schedule(task, delay):從現在起過
delay
毫秒執行一次
QQ截圖20180609194229.png
scheduleAtFixedRate(task,delay,period)
和
schedule(task,delay,period)
基本一樣
scheduleAtFixedRate(task,firstTime,period)
schedule(task,firstTime,period)
差別可以參考:
https://blog.csdn.net/gtuu0123/article/details/6040159測試結果:
QQ截圖20180609174352.png
每隔兩秒輸出一次時間,第二個參數在截圖上展現不了,需要自己敲出來感受。
注:時效性要求較高的多任務并發作業,複雜的任務的排程不推薦使用
Spring Task
@Configuration
@EnableScheduling
public class SchedulingConfig {
@Scheduled(cron = "0/5 * * * * ?") // 每5秒執行一次
public void Scheduled(){
System.out.println("每5秒在控制台列印一次"+new Date());
}
}
結果如下
Paste_Image.png
spring task 在計算時間的時候,是根據目前伺服器的系統時間進行計算.
如果是每10秒執行一次的話,那麼它是從系統時間的0,10,20秒進行計算的.
如果是每1分鐘執行一次的話,那麼它是從系統時間的1分鐘,2分鐘進行計算的
如何動态修改
cron
參數
代碼解釋 一開始設定的是每20秒執行一次,隻要通路下
http://localhost:8080/changeExpression就可以改成每10秒執行一次了
@RestController
@EnableScheduling
public class ConfigTask implements SchedulingConfigurer{
private String expression="0/20 * * * * *";
@RequestMapping("/changeExpression")
public String changeExpression(){
expression="0/10 * * * * *";
return "changeExpression";
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
Runnable task=new Runnable() {
@Override
public void run() {
System.out.println("TaskCronChange task is running ... "+ new Date());
}
};
Trigger trigger=new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger cronTrigger=new CronTrigger(expression);
return cronTrigger.nextExecutionTime(triggerContext);
}
};
scheduledTaskRegistrar.addTriggerTask(task,trigger);
}
}
.nextExecutionTim()
方法是為了傳回一個date類型的資料
看起來很多代碼,其實就是先實作
SchedulingConfigurer
實作裡面的方法,縮小成就這樣
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
Runnable task=new Runnable() {...};
Trigger trigger=new Trigger() {...};
scheduledTaskRegistrar.addTriggerTask(task,trigger);
}
項目需求:
如果有個這樣的場景,在某一段時間内要執行特定的操作
@RestController
@EnableScheduling
public class ConfigTask implements SchedulingConfigurer{
private String expression="0/20 * * * * *";
@RequestMapping("/changeExpression")
public String changeExpression(){
expression="0/10 * * * * *";
return "changeExpression";
}
public void println(long start,long end){
Date date=new Date();
long time=date.getTime()/1000;
if(start<time&&time<end){
System.out.println("TaskCronChange task is running ... "+ new Date());
}else {
System.out.println("TaskCronChange task is stopping ... "+ new Date());
}
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
Runnable task=new Runnable() {
@Override
public void run() {
println(1494306514,1494306634);
}
};
Trigger trigger=new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger cronTrigger=new CronTrigger(expression);
return cronTrigger.nextExecutionTime(triggerContext);
}
};
scheduledTaskRegistrar.addTriggerTask(task,trigger);
}
}
把時間轉化成時間戳,再去對比時間
動态添加修改删除定時任務
1.首先需要重新認識一個類
ThreadPoolTaskScheduler
:線程池任務排程類,能夠開啟線程池進行任務排程。
2.
ThreadPoolTaskScheduler.schedule()
方法會建立一個定時計劃
ScheduledFuture
,在這個方法需要添加兩個參數,
Runnable
(線程接口類) 和
CronTrigger
(定時任務觸發器)
3.在
ScheduledFuture
中有一個cancel可以停止定時任務。
@RestController
@EnableScheduling
public class DynamicTask {
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private ScheduledFuture<?> future;
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
@RequestMapping("/startCron")
public String startCron() {
future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("0/5 * * * * *"));
System.out.println("DynamicTask.startCron()");
return "startCron";
}
@RequestMapping("/stopCron")
public String stopCron() {
if (future != null) {
future.cancel(true);
}
System.out.println("DynamicTask.stopCron()");
return "stopCron";
}
@RequestMapping("/changeCron10")
public String startCron10() {
stopCron();// 先停止,在開啟.
future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("*//**//*10 * * * * *"));
System.out.println("DynamicTask.startCron10()");
return "changeCron10";
}
private class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("DynamicTask.MyRunnable.run()," + new Date());
}
}
}
在私有類
MyRunnable
中的
run
方法中可以寫入具體的定時任務邏輯
(a)我們首先了一個類
DynamicTask
;
(b)定義了兩個變量,
threadPoolTaskScheduler
future
其中
future
是
treadPoolTaskScheduler
執行方法
schedule
的傳回值,主要用于定時任務的停止。
(c)編寫啟動定時器的方法
startCron()
;
(d)編寫停止方法
stopCron()
,這裡編碼的時候,需要注意下需要判斷下
future
為
null
的時候,不然就很容易抛出
NullPointerException
(e)編寫修改定時任務執行周期方法
changeCron10()
,這裡的原理就是關閉之前的定時器,創新在建立一個新的定時器。
Quartz
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,是一個完全由Java編寫的開源作業排程架構。
官網位址:
http://www.quartz-scheduler.org/為在Java應用程式中進行作業排程提供了簡單卻強大的機制。Quartz允許開發人員根據時間間隔來排程作業。它實作了作業和觸發器的多對多的關系,還能把多個作業與不同的觸發器關聯。
Quartz的特點
① 強大的排程功能,spring預設的排程架構, Quartz 很容易與 Spring 內建實作靈活可配置的排程功能,及時系統因故障關閉,任務排程現場的資料并不會丢失
② 靈活的應用方式,運作定義觸發器的排程時間表,并可以對觸發器和任務進行關聯映射,Quartz提供了元件式的監聽器,支援任務和排程的多種組合方式,支援排程資料的多種存儲方式
③ 分布式和叢集能力,Terracotta 收購後在原來功能基礎上作了進一步提升。
Quartz核心概念
scheduler:任務排程器
trigger:觸發器,用于定義任務排程時間規則
job:任務,即被排程的任務
Quartz任務排程基本實作原理
Quartz 任務排程的核心元素是
scheduler
,
trigger
job
,其中
trigger
job
是任務排程的中繼資料,
scheduler
是實際執行排程的控制器。
trigger 是用于定義排程時間的元素,即按照什麼時間規則去執行任務。
Quartz 中主要提供了四種類型的 trigger:
SimpleTrigger
,
CronTirgger
DateIntervalTrigger
,和
NthIncludedDayTrigger
。這四種 trigger 可以滿足企業應用中的絕大部分需求。
job用于表示被排程的任務。
主要有兩種類型的 job:無狀态的(stateless)和有狀态的(stateful)
對于同一個
trigger
來說,有狀态的
job
不能被并行執行,隻有上一次觸發的任務被執行完之後,才能觸發下一次執行。
Job 主要有兩種屬性:
volatility
durability
volatility
表示任務是否被持久化到資料庫存儲,而
durability
表示在沒有
trigger
關聯的時候任務是否被保留。兩者都是在值為
true
的時候任務被持久化或保留。
一個
job
可以被多個
trigger
關聯,但是一個
trigger
隻能關聯一個
job
。
scheduler
由
scheduler
工廠建立:
DirectSchedulerFactory
或者
StdSchedulerFactory
StdSchedulerFactory
使用較多,因為
DirectSchedulerFactory
使用起來不夠友善,需要作許多詳細的手工編碼設定。
Scheduler
主要有三種:
RemoteMBeanScheduler
RemoteScheduler
StdScheduler
在maven項目中使用Quartz
在
pom.xml
檔案中添加
quartz
的依賴:
版本(Apr 19, 2017)
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
首先我們需要定義一個任務類,該類需要繼承
Job
類,
添加
execute(JobExecutionContext context)
方法,在這個方法中就是我們具體的任務執行的地方。
public class HelloJob implements Job{
@Override
public void execute(JobExecutionContext jobExecutionContext)
throws JobExecutionException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(df.format(System.currentTimeMillis()));
}
}
任務排程
public class APP {
public static void main(String[] args) throws
SchedulerException, InterruptedException {
// 擷取Scheduler執行個體
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
//具體任務.
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1","group1").build();
//觸發時間點. (每5秒執行1次.)
SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder
.simpleSchedule().withIntervalInSeconds(5).repeatForever();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") .startNow()
.withSchedule(simpleScheduleBuilder).build();
// 交由Scheduler安排觸發
scheduler.scheduleJob(jobDetail,trigger);
}
}
QQ截圖20180609210103.png
結果就是每隔5秒輸出一次時間此外還會輸出關于觸發器的一些資訊