在工作中遇到一個需求,需要定時自動執(zhí)行某項(xiàng)功能,這就需要用到定時任務(wù)了育叁。
定時任務(wù)調(diào)度:基于給定的時間點(diǎn)洼怔,給定的時間間隔或者給定的執(zhí)行次數(shù)自動執(zhí)行任務(wù)。
定時任務(wù)調(diào)度的幾種實(shí)現(xiàn)方式:
Timer:
Timer由JDK自帶,不需要引入多余的jar框都。Java自帶的java.util.Timer
類,這個類允許你調(diào)度一個java.util.TimerTask
任務(wù)呵晨。Timer只有一個后臺線程執(zhí)行任務(wù),使用這種方式可以讓你的程序按照某一個頻度執(zhí)行魏保,但不能在指定時間運(yùn)行。一般用的較少摸屠。
Quartz:
使用Quartz谓罗,這是一個功能比較強(qiáng)大的的調(diào)度器,可以讓你的程序在指定時間執(zhí)行季二,也可以按照某一個頻度執(zhí)行妥衣,配置起來稍顯復(fù)雜,Quartz擁有后臺執(zhí)行線程池能夠使用多個線程。
Spring Task:
Spring3.0以后自帶的task戒傻,可以將它看成一個輕量級的Quartz税手,而且使用起來比Quartz簡單許多。
Timer代碼實(shí)現(xiàn)
首先寫個需要定時實(shí)現(xiàn)的邏輯,這里是簡單輸出格式化后的時間
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()));
}
}
然后寫定時任務(wù)調(diào)度類
public class MyTimer {
public static void main(String[] args){
//創(chuàng)建timer實(shí)例
Timer timer=new Timer();
//創(chuàng)建一個MyTimerTask實(shí)例
MyTimerTask myTimerTask=new MyTimerTask();
//通過timer定時調(diào)用myTimerTask中的run方法,因?yàn)?TimerTask 類實(shí)現(xiàn)了 Runnable 接口需纳。
timer.schedule(myTimerTask,10*1000,2*1000);
}
}
這里的重點(diǎn)是Timer中schedule()方法的三個參數(shù)
第一個參數(shù):是 就是我們剛剛寫的MyTimerTask
類芦倒,這里需要繼承TimerTask
,并實(shí)現(xiàn) run()
方法不翩,因?yàn)?TimerTask
類實(shí)現(xiàn)了 Runnable
接口兵扬。
第二個參數(shù):是程序啟動后,timer定時器第一次調(diào)用run方法的時間口蝠,0表示不指時間器钟,立刻調(diào)用。(10*1000表示10秒妙蔗,因?yàn)閰?shù)單位是毫秒)
第三個參數(shù):是指第一次調(diào)用之后傲霸,從第二次開始每隔多長的時間調(diào)用一次 run() 方法。單位同樣是毫秒眉反。
拓展:
其實(shí)Timer中schedule()方法一共有四種用法昙啄,剛剛只是其中一種
schedule(task, time):time為
Date
類型,在指定時間執(zhí)行一次寸五。schedule(task, firstTime, period):firstTime為
Date
類型梳凛,從firstTime
時刻開始,每隔period
毫秒執(zhí)行一次梳杏。schedule(task, delay):從現(xiàn)在起過
delay
毫秒執(zhí)行一次scheduleAtFixedRate(task,delay,period)
和schedule(task,delay,period)
基本一樣scheduleAtFixedRate(task,firstTime,period)
和schedule(task,firstTime,period)
基本一樣區(qū)別可以參考:https://blog.csdn.net/gtuu0123/article/details/6040159
測試結(jié)果:
每隔兩秒輸出一次時間韧拒,第二個參數(shù)在截圖上體現(xiàn)不了淹接,需要自己敲出來感受。
注:時效性要求較高的多任務(wù)并發(fā)作業(yè)叛溢,復(fù)雜的任務(wù)的調(diào)度不推薦使用
Spring Task
@Configuration
@EnableScheduling
public class SchedulingConfig {
@Scheduled(cron = "0/5 * * * * ?") // 每5秒執(zhí)行一次
public void Scheduled(){
System.out.println("每5秒在控制臺打印一次"+new Date());
}
}
結(jié)果如下
spring task 在計(jì)算時間的時候蹈集,是根據(jù)當(dāng)前服務(wù)器的系統(tǒng)時間進(jìn)行計(jì)算.
如果是每10秒執(zhí)行一次的話,那么它是從系統(tǒng)時間的0,10,20秒進(jìn)行計(jì)算的.
如果是每1分鐘執(zhí)行一次的話雇初,那么它是從系統(tǒng)時間的1分鐘拢肆,2分鐘進(jìn)行計(jì)算的
如何動態(tài)修改cron
參數(shù)
代碼解釋 一開始設(shè)定的是每20秒執(zhí)行一次,只要訪問下
http://localhost:8080/changeExpression
就可以改成每10秒執(zhí)行一次了
@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類型的數(shù)據(jù)
看起來很多代碼靖诗,其實(shí)就是先實(shí)現(xiàn)SchedulingConfigurer
接口郭怪。
實(shí)現(xiàn)里面的方法,縮小成就這樣
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
Runnable task=new Runnable() {...};
Trigger trigger=new Trigger() {...};
scheduledTaskRegistrar.addTriggerTask(task,trigger);
}
項(xiàng)目需求:
如果有個這樣的場景刊橘,在某一段時間內(nèi)要執(zhí)行特定的操作
@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);
}
}
把時間轉(zhuǎn)化成時間戳鄙才,再去對比時間
結(jié)果如下
動態(tài)添加修改刪除定時任務(wù)
1.首先需要重新認(rèn)識一個類ThreadPoolTaskScheduler
:線程池任務(wù)調(diào)度類,能夠開啟線程池進(jìn)行任務(wù)調(diào)度促绵。
2.ThreadPoolTaskScheduler.schedule()
方法會創(chuàng)建一個定時計(jì)劃ScheduledFuture
攒庵,在這個方法需要添加兩個參數(shù),Runnable
(線程接口類) 和CronTrigger
(定時任務(wù)觸發(fā)器)
3.在ScheduledFuture
中有一個cancel可以停止定時任務(wù)败晴。
@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
方法中可以寫入具體的定時任務(wù)邏輯
(a)我們首先了一個類DynamicTask
;
(b)定義了兩個變量尖坤,threadPoolTaskScheduler
和future
其中future
是treadPoolTaskScheduler
執(zhí)行方法schedule
的返回值稳懒,主要用于定時任務(wù)的停止。
(c)編寫啟動定時器的方法startCron()
慢味;
(d)編寫停止方法stopCron()
,這里編碼的時候场梆,需要注意下需要判斷下future
為null
的時候,不然就很容易拋出NullPointerException
纯路;
(e)編寫修改定時任務(wù)執(zhí)行周期方法changeCron10()
或油,這里的原理就是關(guān)閉之前的定時器,創(chuàng)新在創(chuàng)建一個新的定時器驰唬。
Quartz
Quartz是OpenSymphony開源組織在Job scheduling領(lǐng)域又一個開源項(xiàng)目顶岸,是一個完全由Java編寫的開源作業(yè)調(diào)度框架。
官網(wǎng)地址:http://www.quartz-scheduler.org/
為在Java應(yīng)用程序中進(jìn)行作業(yè)調(diào)度提供了簡單卻強(qiáng)大的機(jī)制定嗓。Quartz允許開發(fā)人員根據(jù)時間間隔來調(diào)度作業(yè)蜕琴。它實(shí)現(xiàn)了作業(yè)和觸發(fā)器的多對多的關(guān)系,還能把多個作業(yè)與不同的觸發(fā)器關(guān)聯(lián)宵溅。
Quartz的特點(diǎn)
① 強(qiáng)大的調(diào)度功能,spring默認(rèn)的調(diào)度框架上炎, Quartz 很容易與 Spring 集成實(shí)現(xiàn)靈活可配置的調(diào)度功能恃逻,及時系統(tǒng)因故障關(guān)閉雏搂,任務(wù)調(diào)度現(xiàn)場的數(shù)據(jù)并不會丟失
② 靈活的應(yīng)用方式,運(yùn)行定義觸發(fā)器的調(diào)度時間表寇损,并可以對觸發(fā)器和任務(wù)進(jìn)行關(guān)聯(lián)映射凸郑,Quartz提供了組件式的監(jiān)聽器,支持任務(wù)和調(diào)度的多種組合方式矛市,支持調(diào)度數(shù)據(jù)的多種存儲方式
③ 分布式和集群能力芙沥,Terracotta 收購后在原來功能基礎(chǔ)上作了進(jìn)一步提升。
Quartz核心概念
scheduler:任務(wù)調(diào)度器
trigger:觸發(fā)器浊吏,用于定義任務(wù)調(diào)度時間規(guī)則
job:任務(wù)而昨,即被調(diào)度的任務(wù)
Quartz任務(wù)調(diào)度基本實(shí)現(xiàn)原理
Quartz 任務(wù)調(diào)度的核心元素是 scheduler
, trigger
和job
,其中 trigger
和 job
是任務(wù)調(diào)度的元數(shù)據(jù)找田, scheduler
是實(shí)際執(zhí)行調(diào)度的控制器歌憨。
trigger 是用于定義調(diào)度時間的元素,即按照什么時間規(guī)則去執(zhí)行任務(wù)墩衙。
Quartz 中主要提供了四種類型的 trigger:SimpleTrigger
务嫡,CronTirgger
,DateIntervalTrigger
漆改,和 NthIncludedDayTrigger
心铃。這四種 trigger 可以滿足企業(yè)應(yīng)用中的絕大部分需求。
job用于表示被調(diào)度的任務(wù)挫剑。
主要有兩種類型的 job:無狀態(tài)的(stateless)和有狀態(tài)的(stateful)
對于同一個trigger
來說于个,有狀態(tài)的 job
不能被并行執(zhí)行,只有上一次觸發(fā)的任務(wù)被執(zhí)行完之后暮顺,才能觸發(fā)下一次執(zhí)行厅篓。
Job 主要有兩種屬性:volatility
和 durability
,其中 volatility
表示任務(wù)是否被持久化到數(shù)據(jù)庫存儲捶码,而 durability
表示在沒有trigger
關(guān)聯(lián)的時候任務(wù)是否被保留羽氮。兩者都是在值為true
的時候任務(wù)被持久化或保留。
一個job
可以被多個trigger
關(guān)聯(lián)惫恼,但是一個trigger
只能關(guān)聯(lián)一個job
档押。
scheduler
由 scheduler
工廠創(chuàng)建:DirectSchedulerFactory
或者 StdSchedulerFactory
。
StdSchedulerFactory
使用較多祈纯,因?yàn)?DirectSchedulerFactory
使用起來不夠方便令宿,需要作許多詳細(xì)的手工編碼設(shè)置。
Scheduler
主要有三種:RemoteMBeanScheduler
腕窥, RemoteScheduler
和 StdScheduler
粒没。
在maven項(xiàng)目中使用Quartz
在pom.xml
文件中添加quartz
的依賴:
版本(Apr 19, 2017)
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
首先我們需要定義一個任務(wù)類,該類需要繼承Job
類簇爆,
添加execute(JobExecutionContext context)
方法癞松,在這個方法中就是我們具體的任務(wù)執(zhí)行的地方爽撒。
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()));
}
}
任務(wù)調(diào)度
public class APP {
public static void main(String[] args) throws
SchedulerException, InterruptedException {
// 獲取Scheduler實(shí)例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
//具體任務(wù).
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1","group1").build();
//觸發(fā)時間點(diǎn). (每5秒執(zhí)行1次.)
SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder
.simpleSchedule().withIntervalInSeconds(5).repeatForever();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1") .startNow()
.withSchedule(simpleScheduleBuilder).build();
// 交由Scheduler安排觸發(fā)
scheduler.scheduleJob(jobDetail,trigger);
}
}
測試結(jié)果:
結(jié)果就是每隔5秒輸出一次時間此外還會輸出關(guān)于觸發(fā)器的一些信息