引言
在實際項目開發(fā)中,定時任務調(diào)度是經(jīng)常會出現(xiàn)的一類需求毛秘。
定時任務的場景可以說非常廣泛,例如:
- 購買某些視頻網(wǎng)站的會員后阻课,每天給會員送成長值叫挟,每月給會員送電影券
- 在保證最終一致性的場景中,利用定時任務調(diào)度進行一些數(shù)據(jù)核對的工作
- 通過郵件定時發(fā)送報表和工作提醒
- 需要定時清理數(shù)據(jù)的任務
本文將介紹單機定時任務的基本實現(xiàn)方式限煞,可以覆蓋到定時任務調(diào)度最基本的使用場景:包括:
- Timer與TimerTask
- ScheduledExecutorService
- Spring Task
- Quartz
正文
方式一:JDK原生定時工具:Timer
簡介
JDK提供的Timer類抹恳,允許調(diào)度一個TimerTask任務。Timer位于java.util包下署驻,其內(nèi)部包含且僅包含一個后臺線程(TimeThread)對多個業(yè)務任務(TimeTask)進行定時定頻率的調(diào)度。
schedule的四種用法和scheduleAtFixedRate的兩種用法:
public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task, Date time);
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date firstTime, long period);
public void scheduleAtFiexRate(TimerTask task, long delay, long period);
public void scheduleAtFiexRate(TimerTask task, Date firstTime, long period);
參數(shù)說明:
- task:所要執(zhí)行的任務,需要實現(xiàn)TimeTask的run()方法
- time/firstTime:首次執(zhí)行任務的時間
- period:周期性執(zhí)行Task的時間間隔速警,單位是毫秒
- delay:執(zhí)行task任務前的延時時間肠缔,單位是毫秒
很顯然,通過上述的描述宣吱,我們可以實現(xiàn):
- 延遲多久后執(zhí)行一次任務
- 指定時間執(zhí)行一次任務
- 延遲一段時間窃这,并周期性執(zhí)行任務
- 指定時間,并周期性執(zhí)行任務
代碼示例
編寫MyTimerTask任務類征候,用以表示具體需要執(zhí)行的任務:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
public class MyTimerTask extends TimerTask {
/**
* The action to be performed by this timer task.
*/
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private AtomicInteger count = new AtomicInteger(0);
public void run() {
System.out.println("執(zhí)行時間:" + FORMAT.format(this.scheduledExecutionTime()));
count.incrementAndGet();
if (count.get() == 10) {
this.cancel();
System.out.println("達到預定執(zhí)行次數(shù)杭攻,取消執(zhí)行計劃");
}
}
}
編寫TimerDemo類,為需要調(diào)度的任務設置調(diào)度運行參數(shù):
package com.netease.scaffold.task;
import java.util.Date;
import java.util.Timer;
public class TimerDemo {
public static void main(String[] args) {
// 創(chuàng)建定時器
Timer timer = new Timer();
// 添加調(diào)度任務
// schedule(TimerTask task, Date time); 特定時間 time 執(zhí)行
// timer.schedule(new MyTimerTask(), new Date(System.currentTimeMillis() + 1000));
// schedule(TimerTask task, long delay); //延遲 delay毫秒 執(zhí)行 task
// timer.schedule(new MyTimerTask(), 1000);
// schedule(TimerTask task, long delay, long period) 延遲 delay毫秒 執(zhí)行并每隔 period毫秒 執(zhí)行一次
// timer.schedule(new MyTimerTask(), 1000, 5000);
// schedule(TimerTask task, Date time, long period); 特定時間 time 執(zhí)行并每隔 period毫秒 執(zhí)行一次
timer.schedule(new MyTimerTask(), new Date(System.currentTimeMillis() + 1000), 1000);
}
}
測試結(jié)果
執(zhí)行時間:2018-12-25 11:58:00
執(zhí)行時間:2018-12-25 11:58:01
執(zhí)行時間:2018-12-25 11:58:02
執(zhí)行時間:2018-12-25 11:58:03
執(zhí)行時間:2018-12-25 11:58:04
執(zhí)行時間:2018-12-25 11:58:05
執(zhí)行時間:2018-12-25 11:58:06
執(zhí)行時間:2018-12-25 11:58:07
執(zhí)行時間:2018-12-25 11:58:08
執(zhí)行時間:2018-12-25 11:58:09
達到預定執(zhí)行次數(shù)疤坝,取消執(zhí)行計劃
不難發(fā)現(xiàn)兆解,輸出是在當前執(zhí)行時刻延遲1秒后開始執(zhí)行的,后面每隔1秒執(zhí)行一次跑揉。達到計劃的執(zhí)行次數(shù)之后锅睛,取消執(zhí)行計劃。
點評
思考1:如果time/firstTime指定的時間历谍,在當前時間之前衣撬,會發(fā)生什么呢?
在時間等于或者超過time/firstTime的時候扮饶,會執(zhí)行task具练!也就是說,如果time/firstTime指定的時間在當前時間之前甜无,就會立即得到執(zhí)行扛点。
思考2:schedule和scheduleAtFixedRate有什么區(qū)別哥遮?
scheduleAtFixedRate:每次執(zhí)行時間為上一次任務開始起向后推一個period間隔,也就是說下次執(zhí)行時間相對于上一次任務開始的時間點陵究,因此執(zhí)行時間不會延后眠饮,但是存在任務并發(fā)執(zhí)行的問題。
schedule:每次執(zhí)行時間為上一次任務結(jié)束后推一個period間隔铜邮,也就是說下次執(zhí)行時間相對于上一次任務結(jié)束的時間點仪召,因此執(zhí)行時間會不斷延后。
思考3:如果執(zhí)行task發(fā)生異常松蒜,是否會影響其他task的定時調(diào)度扔茅?
如果TimeTask拋出RuntimeException,那么Timer會停止所有任務的運行秸苗!
思考4:Timer的一些缺陷召娜?
前面已經(jīng)提及到Timer背后是一個單線程,因此Timer存在管理并發(fā)任務的缺陷:所有任務都是由同一個線程來調(diào)度惊楼,所有任務都是串行執(zhí)行玖瘸,意味著同一時間只能有一個任務得到執(zhí)行,而前一個任務的延遲或者異常會影響到之后的任務檀咙。
其次雅倒,Timer的一些調(diào)度方式還算比較簡單,無法適應實際項目中任務定時調(diào)度的復雜度弧可。
這種只適合一些最基礎的定時任務屯断,作為玩具使用,在實際的項目開發(fā)中一般很少用到侣诺,了解即可殖演。
方式二:JDK對定時任務調(diào)度的線程池支持:ScheduledExecutorService
由于Timer存在的問題,JDK5之后便提供了基于線程池的定時任務調(diào)度ScheduledExecutorService
年鸳。它的設計理念是每一個被調(diào)度的任務都會被線程池中的一個線程去執(zhí)行趴久,因此任務可以并發(fā)執(zhí)行,而且相互之間不受影響搔确。
編寫代碼ScheduleExecutorServiceDemo
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduleExecutorServiceDemo implements Runnable {
@Override
public void run() {
System.out.println("執(zhí)行:" + new Date());
}
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(new ScheduleExecutorServiceDemo(), 1000, 2000, TimeUnit.MILLISECONDS);
}
}
測試結(jié)果
執(zhí)行:Sun Mar 29 17:09:36 CST 2020
執(zhí)行:Sun Mar 29 17:09:38 CST 2020
執(zhí)行:Sun Mar 29 17:09:40 CST 2020
執(zhí)行:Sun Mar 29 17:09:42 CST 2020
...
方式三:Spring Task
Spring也提供了對于每臺機器都執(zhí)行的定時任務的支持彼棍,熟悉Spring的同學都知道Spring一般都是同時支持XML配置和注解配置的方式的,下面我們將分別對這兩種方式分別介紹膳算。
XML配置方式
添加配置
<!--1. 基于配置的Spring Task-->
<bean id="springTask" class="com.netease.scaffold.task.SpringTask"/>
<!--注冊調(diào)度任務-->
<task:scheduled-tasks>
<!--延遲1秒 執(zhí)行任務-->
<!--<task:scheduled ref="springTask" method="show1" fixed-delay="1000" />-->
<!--固定速度3秒 執(zhí)行任務-->
<task:scheduled ref="springTask" method="show1" fixed-rate="3000"/>
<!--
使用cron表達式 指定觸發(fā)時間
spring task 只支持6位的cron表達式 秒 分 時 日 月 星期
-->
<!--cron表達式座硕,每秒 執(zhí)行任務-->
<task:scheduled ref="springTask" method="show2" cron="*/1 * * * * ?"/>
</task:scheduled-tasks>
<!--執(zhí)行器配置-->
<task:executor id="threadPoolTaskExecutor" pool-size="10" keep-alive="5"/>
<!--調(diào)度器配置-->
<task:scheduler id="threadPoolTaskScheduler" pool-size="10"/>
編寫測試任務類SpringTask
package com.netease.scaffold.task;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SpringTask {
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void show1() {
System.out.println("show1:" + FORMAT.format(new Date()));
}
public void show2() {
System.out.println("show2:" + FORMAT.format(new Date()));
}
}
測試結(jié)果
show2:2018-12-05 16:01:03
show2:2018-12-05 16:01:04
show1:2018-12-05 16:01:04
show2:2018-12-05 16:01:05
show2:2018-12-05 16:01:06
show2:2018-12-05 16:01:07
show1:2018-12-05 16:01:07
show2:2018-12-05 16:01:08
show2:2018-12-05 16:01:09
show2:2018-12-05 16:01:10
show1:2018-12-05 16:01:10
show2:2018-12-05 16:01:11
show2:2018-12-05 16:01:12
show2:2018-12-05 16:01:13
show1:2018-12-05 16:01:13
show2:2018-12-05 16:01:14
show2:2018-12-05 16:01:15
show2:2018-12-05 16:01:16
show1:2018-12-05 16:01:16
show2:2018-12-05 16:01:17
注解方式
定時任務啟用注解
首先,需要在應用入口類上加上@EnableScheduling
注解涕蜂,表示啟用注解掃描方式的定時任務华匾。
測試類任務類SpringAnnoTask
package com.netease.scaffold.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* spring執(zhí)行任務的類
*/
@Component
public class SpringAnnoTask {
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Scheduled(cron = "1-10 * * * * ? ")//每分鐘的1-10秒每秒執(zhí)行一次
public void show1() {
System.out.println("show1:" + FORMAT.format(new Date()));
}
@Scheduled(cron = "0/10 * * * * ? ")//每10秒執(zhí)行一次
public void show2() {
System.out.println("show2:" + FORMAT.format(new Date()));
}
@Scheduled(fixedRate = 2000)//每兩秒執(zhí)行一次時間
public void show3() {
System.out.println("show3:" + FORMAT.format(new Date()));
}
@Scheduled(fixedDelay = 4000)//每次任務執(zhí)行完之后的4s后繼續(xù)執(zhí)行
public void show4() {
System.out.println("show4:" + FORMAT.format(new Date()));
}
}
測試結(jié)果
show2:2018-12-05 16:23:54
show1:2018-12-05 16:23:54
show3:2018-12-05 16:23:54
show2:2018-12-05 16:23:55
show2:2018-12-05 16:23:56
show3:2018-12-05 16:23:56
show4:2018-12-05 16:23:56
show2:2018-12-05 16:23:57
show1:2018-12-05 16:23:57
show2:2018-12-05 16:23:58
show3:2018-12-05 16:23:58
show2:2018-12-05 16:23:59
show2:2018-12-05 16:24:00
show2:2018-12-05 16:24:00
show1:2018-12-05 16:24:00
show3:2018-12-05 16:24:00
show4:2018-12-05 16:24:00
show2:2018-12-05 16:24:01
show1:2018-12-05 16:24:01
show2:2018-12-05 16:24:02
show1:2018-12-05 16:24:02
點評
spring task的特點:
- 默認單線程同步執(zhí)行
- 一個任務執(zhí)行完上一次之后,才會執(zhí)行下一次調(diào)度
- 多任務之間按順序執(zhí)行机隙,一個任務執(zhí)行完成之后才會執(zhí)行另一個任務
- 多任務并行執(zhí)行需要設置線程池
- 全程可以通過注解配置
方式四:Quartz框架
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目蜘拉,是完全由java開發(fā)的一個開源的任務日程管理系統(tǒng)萨西,“任務進度管理器”就是一個在預先確定(被納入日程)的時間到達時,負責執(zhí)行(或者通知)其他軟件組件的系統(tǒng)旭旭。雖然ScheduledExecutorService對Timer進行了線程池的改進谎脯,但是依然無法滿足復雜的定時任務調(diào)度場景。因此OpenSymphony提供了強大的開源任務調(diào)度框架:Quartz持寄。Quartz是純Java實現(xiàn)源梭,而且作為Spring的默認調(diào)度框架,由于Quartz的強大的調(diào)度功能稍味、靈活的使用方式废麻、還具有分布式集群能力,可以說Quartz出馬仲闽,可以搞定一切定時任務調(diào)度!
特點
- 強大的調(diào)度功能僵朗,例如支持豐富多樣的調(diào)度方法赖欣,可以滿足各種常規(guī)及特殊需求;
- 靈活的應用方式验庙,例如支持任務和調(diào)度的多種組合方式顶吮,支持調(diào)度數(shù)據(jù)的多種存儲方式;
- 分布式和集群能力粪薛,Terracotta 收購后在原來功能基礎上作了進一步提升悴了。
- 另外,作為 Spring 默認的調(diào)度框架违寿,Quartz 很容易與 Spring 集成實現(xiàn)靈活可配置的調(diào)度功能湃交。
核心元素 :
- Quartz有3個核心概念:調(diào)度器(Scheduler)、任務(Job&JobDetail)藤巢、觸發(fā)器(Trigger)搞莺。(一個任務可以被多個觸發(fā)器觸發(fā),一個觸發(fā)器只能觸發(fā)一個任務)
- Scheduler: 任務調(diào)度器掂咒,是實際執(zhí)行任務調(diào)度的控制器才沧。在spring中通過SchedulerFactoryBean封裝起來。
- Trigger :觸發(fā)器绍刮,用于定義任務調(diào)度的時間規(guī)則温圆,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,其中CronTrigger用的比較多孩革,本文主要介紹這種方式岁歉。CronTrigger在spring中封裝在CronTriggerFactoryBean中。
- Calendar:它是一些日歷特定時間點的集合膝蜈。一個trigger可以包含多個Calendar刨裆,以便排除或包含某些時間點澈圈。
- **Job **:任務,是一個接口帆啃,只有一個方法void execute(JobExecutionContext context),開發(fā)者實現(xiàn)該接口定義運行任務瞬女,JobExecutionContext類提供了調(diào)度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中努潘。實現(xiàn)Job接口的任務诽偷,默認是無狀態(tài)的,若要將Job設置成有狀態(tài)的疯坤,在quartz中是給實現(xiàn)的Job添加@DisallowConcurrentExecution注解(以前是實現(xiàn)StatefulJob接口报慕,現(xiàn)在已被Deprecated),在與spring結(jié)合中可以在spring配置文件的job detail中配置concurrent參數(shù)压怠。
- JobDetail :任務信息眠冈,用來描述Job實現(xiàn)類及其它相關的靜態(tài)信息,如Job名字菌瘫、關聯(lián)監(jiān)聽器等信息蜗顽。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean兩種實現(xiàn),如果任務調(diào)度只需要執(zhí)行某個類的某個方法雨让,就可以通過MethodInvokingJobDetailFactoryBean來調(diào)用雇盖。
- 注意當Scheduler調(diào)度Job時,實際上會通過反射newInstance一個新的Job實例(待調(diào)度完畢后銷毀掉)栖忠,同時會把JobExecutionContext傳遞給Job的execute方法崔挖,Job實例通過JobExecutionContext訪問到Quartz運行時的環(huán)境以及Job本身的明細數(shù)據(jù)。
- JobDataMap可以裝載任何可以序列化的數(shù)據(jù)庵寞,存取很方便狸相。需要注意的是JobDetail和Trigger都可以各自關聯(lián)上JobDataMap。JobDataMap除了可以通過上述代碼獲取外捐川,還可以在YourJob實現(xiàn)類中卷哩,添加相應setter方法獲取。
- 實際上属拾,Quartz在進行調(diào)度器初始化的時候将谊,會加載quartz.properties文件進行一些屬性的設置,比如Quartz后臺線程池的屬性(threadCount)渐白、作業(yè)存儲設置等尊浓。它會先從工程中找,如果找不到那么就是用quartz.jar中的默認的quartz.properties文件纯衍。
- Quartz存在監(jiān)聽器的概念栋齿,比如任務執(zhí)行前后、任務的添加等,可以方便實現(xiàn)任務的監(jiān)控瓦堵。
Trigger觸發(fā)器 :
Trigger用來告訴Quartz調(diào)度程序什么時候執(zhí)行基协,常用的觸發(fā)器有2種:SimpleTrigger(類似于Timer)、CronTrigger(類似于Linux的Crontab)菇用。
- SimpleTrigger :在一個指定時間段內(nèi)執(zhí)行一次作業(yè)任務或是在指定時間間隔內(nèi)執(zhí)行多次作業(yè)任務澜驮;
- CronTrigger :基于日歷的作業(yè)調(diào)度器,而不是像SimpleTrigger那樣精確指定間隔時間惋鸥,比SimpleTrigger更常用杂穷。
引入依賴包
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
添加配置信息
在Spring的配置文件ApplicationContext.xml添加如下的信息:
<!--引入其他配置參數(shù).如果參數(shù)文件不止一個,由于spring容器只會維護一個PropertyPlaceholderConfigurer的bean實例卦绣,當spring發(fā)現(xiàn)容器中有一個該實例后耐量,就會忽略其余的,所以滤港,該標簽只能配置一個廊蜒,多余的spring會自動忽略。需要加上這個設置ignore-unresolvable="true"溅漾,否則會報錯-->
<context:property-placeholder location="classpath:scaffold.properties" ignore-unresolvable="true"/>
<!--===============================Quartz定時任務配置=========================================-->
<!--調(diào)度任務1:實現(xiàn)job接口山叮,可獲取job上下文信息-->
<bean id="simpleTriggerJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.netease.scaffold.task.SimpleTriggerJob"/>
<property name="jobDataAsMap">
<map>
<entry key="triggerMessage1" value="Job Message In JobDetail"/> <!--設置JobDetail中的值-->
</map>
</property>
</bean>
<!--調(diào)度觸發(fā)器-->
<!--觸發(fā)器1:SimpleTrigger:每隔多少分鐘小時執(zhí)行,簡單重復執(zhí)行的場景下可用-->
<bean id="simpleTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="simpleTriggerJob"/> <!--觸發(fā)的job引用-->
<property name="startDelay" value="1000"/> <!--設置延遲1秒后運行-->
<property name="repeatInterval" value="1000"/> <!--設置每1秒觸發(fā)一次-->
<property name="jobDataAsMap">
<map>
<entry key="triggerMessage2" value="Job Message From Trigger"/> <!--設置Trigger中的值-->
</map>
</property>
</bean>
<!--調(diào)度任務2:自定義的bean,無任何限制-->
<bean id="cronTriggerJob" class="com.netease.scaffold.task.CronTriggerJob">
<property name="taskName" value="${scaffold.app.taskName}"/>
</bean>
<!--通過MethodInvokingJobDetailFactoryBean樟凄,任務類可以不實現(xiàn)job接口聘芜,通過targetMethod指定調(diào)用方法-->
<!--定義目標bean和bean中的方法-->
<bean id="springQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="cronTriggerJob"/>
<!--定義需要定時執(zhí)行的方法名稱-->
<property name="targetMethod" value="execute"/>
</bean>
<!--觸發(fā)器2:CronTrigger:日歷相關的重復時間間隔兄渺,如每天凌晨缝龄,每周星期一運行的話,通過Cron表達式便可定義出復雜的調(diào)度方案挂谍。-->
<bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="springQtzJobMethod"/>
<!-- cron表達式 -->
<property name="cronExpression" value="*/1 * * * * ?"/>
</bean>
<!-- 定時任務調(diào)度工廠 -->
<bean id="task-schedulerFactory" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTriggerBean" />
<ref bean="cronTriggerBean"/>
</list>
</property>
</bean>
定時任務
- 編寫測試定時任務測試代碼SimpleTriggerJob:
package com.netease.scaffold.task;
import java.util.Map;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleTriggerJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Map properties = context.getMergedJobDataMap();
System.out.println("Hello World!");
System.out.println("Previous Fire Time: " + context.getPreviousFireTime());//上次觸發(fā)任務的時間
System.out.println("Current Fire Time: " + context.getFireTime());//當前觸發(fā)時間
System.out.println("Next Fire Time: " + context.getNextFireTime());//下次觸發(fā)時間
System.out.println(properties.get("triggerMessage1"));
System.out.println(properties.get("triggerMessage2"));
System.out.println();
}
}
- 編寫測試定時任務測試代碼CronTriggerJob:
package com.netease.scaffold.task;
import java.util.Date;
public class CronTriggerJob {
private static Integer counter = 0;
private static String taskName;
public static String getTaskName() {
return taskName;
}
public static void setTaskName(String taskName) {
CronTriggerJob.taskName = taskName;
}
protected void execute() {
long ms = System.currentTimeMillis();
System.out.println("taskName:" + taskName);
System.out.println("\t\t" + new Date(ms));
System.out.println("(" + counter++ + ")");
}
}
測試結(jié)果
測試輸出內(nèi)容每秒執(zhí)行一次輸出叔壤,跟預期結(jié)果一致。
(17)
2018-12-05 13:14:41,000 [DEBUG] org.quartz.core.JobRunShell - Calling execute on job DEFAULT.springQtzJobMethod
2018-12-05 13:14:41,000 [DEBUG] org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
taskName:myTaskName
Wed Dec 05 13:14:41 CST 2018
(18)
2018-12-05 13:14:42,000 [DEBUG] org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
2018-12-05 13:14:42,000 [DEBUG] org.quartz.core.JobRunShell - Calling execute on job DEFAULT.springQtzJobMethod
taskName:myTaskName
Wed Dec 05 13:14:42 CST 2018
(19)
2018-12-05 13:14:42,033 [DEBUG] org.quartz.core.JobRunShell - Calling execute on job DEFAULT.simpleTriggerJob
Hello World!
2018-12-05 13:14:42,033 [DEBUG] org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
Previous Fire Time: Wed Dec 05 13:14:39 CST 2018
Current Fire Time: Wed Dec 05 13:14:42 CST 2018
Next Fire Time: Wed Dec 05 13:14:45 CST 2018
Job Message In JobDetail
Job Message From Trigger
使用原生Quartz
編寫QuartzDemo
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzDemo {
public static void main(String[] args) throws SchedulerException {
// 定義執(zhí)行任務
JobDetail jobDetail = JobBuilder.newJob(SimpleTriggerJob.class)
.withIdentity("myjob", "myGroup")
.usingJobData("triggerMessage1", "louxj424")
.usingJobData("triggerMessage2", "zhangsan")
.build();
// 定義簡單執(zhí)行的觸發(fā)器:每兩秒執(zhí)行一次口叙,直到永遠
// Trigger trigger = TriggerBuilder.newTrigger()
// .withIdentity("triggerName", "triggerGroup")
// .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
// .startNow()
// .build();
// 定義cron表示的觸發(fā)器:每兩秒執(zhí)行一次炼绘,直到永遠
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName", "triggerGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ? *"))
.build();
// 定義調(diào)度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
測試
Hello World!
Previous Fire Time: null
Current Fire Time: Tue Dec 25 12:02:12 CST 2018
Next Fire Time: Tue Dec 25 12:02:14 CST 2018
louxj424
zhangsan
Hello World!
Previous Fire Time: Tue Dec 25 12:02:12 CST 2018
Current Fire Time: Tue Dec 25 12:02:14 CST 2018
Next Fire Time: Tue Dec 25 12:02:16 CST 2018
louxj424
zhangsan
Hello World!
Previous Fire Time: Tue Dec 25 12:02:14 CST 2018
Current Fire Time: Tue Dec 25 12:02:16 CST 2018
Next Fire Time: Tue Dec 25 12:02:18 CST 2018
louxj424
zhangsan
cron表達式
Cron表達式是一個字符串,字符串以5或6個空格隔開妄田,分為6或7個域俺亮,每一個域代表一個含義,Cron有如下兩種語法格式:
? (1)Seconds Minutes Hours DayofMonth Month DayofWeek Year
? (2)Seconds Minutes Hours DayofMonth Month DayofWeek
特殊符號說明
特殊字符 | 含義 |
---|---|
* | 表示所有值疟呐。例如在分的字段上設置“*”脚曾,表示每一分鐘都會觸發(fā)。 |
? | 表示不指定值启具。使用場景為不需要關心當前設置的這個字段的值本讥。例如,要在每個月的10號出發(fā)一個操作,但是不關心是周幾拷沸,所以需要在周位置設置為“色查?”,具體設置為“0 0 0 10 * ? *” |
- | 表示區(qū)間。例如撞芍,在小時位置設置10-12秧了,表示10,11勤庐,12都會觸發(fā)示惊。 |
, | 表示指定多個值,例如周字段上設置“MON,WEB,FRI”表示周一愉镰、周三和周五會觸發(fā)米罚。 |
/ | 用于遞增觸發(fā)。如秒上面設置“5/15”丈探,表示從5秒開始录择,每增15秒就觸發(fā)一次,可以計算出來觸發(fā)的時間依次為(5碗降,20隘竭,35,50)讼渊。在月上設置“1/3”表示從每月1號開始动看,每隔三天觸發(fā)一次。 |
L | 表示最后的意思爪幻。在字段上設置菱皆,表示當月最后一天(依據(jù)當前月份),如果是二月還會根據(jù)是否是閏年挨稿。在周字段上表示星期六仇轻,相當于“7”或者“SAT”。如果在L前面加上數(shù)字奶甘,則表示該數(shù)據(jù)的最后一個篷店。例如,在周字段上設置6L臭家,表示本月的最后一個星期五疲陕。 |
W | 表示離指定日期最近的那個工作日(周一至周五)。例如在日字段上設置“15W”钉赁,表示離每月15號最近的那個工作日觸發(fā)蹄殃。如果15號正好是星期六,則最近的是周五14號觸發(fā)橄霉。如果15號是周末窃爷,則找最近的下周一16號觸發(fā)邑蒋。如果15號正好是在工作日(周一到周五),則就在當天觸發(fā)按厘。如果指定格式為“1W”医吊,它表示每月1號往后最近的工作日觸發(fā)。如果1號正好是星期六逮京,則將在下周一也就是3號觸發(fā)卿堂。(注:“W”前只能設置具體的數(shù)字,不允許區(qū)間) |
# | 序號懒棉,表示每月的第幾個周幾草描。例如在周字段上設置“6#3”表示在每月的第三個周五。 |
字段值說明
字段 | 是否必填 | 允許值 | 允許的特殊字符 |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
小時 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * ? / L C |
月 | 是 | 1-12或者JAN-DEC | , - * / |
周 | 是 | 1-7或者SUN-SAT | . - * ? / L # |
年 | 否 | empty策严,1970-2099 | , - * / |
常用表達式舉例
序號 | 表達式 | 說明 |
---|---|---|
1 | 0 15 10 * * ? * | 每天10點15分觸發(fā) |
2 | 0 15 10 * * ? 2017 | 2017年每天10點15分觸發(fā) |
3 | 0 * 14 * * ? | 每天下午的 2點到2點59分每分觸發(fā) |
4 | 0 0/5 14 * * ? | 每天下午的 2點到2點59分(整點開始穗慕,每隔5分觸發(fā)) |
5 | 0 0/5 14,18 * * ? | 每天下午的 2點到2點59分、18點到18點59分(整點開始妻导,每隔5分觸發(fā)) |
6 | 0 0-5 14 * * ? | 每天14點到14點5分內(nèi)每分種觸發(fā)一次 |
7 | 0 15 10 ? * 6L | 每月最后一周的星期五的10點15分觸發(fā) |
8 | 0 15 10 ? * 6#3 | 每月第三個星期五的10點15分觸發(fā) |
其他工具
我們可以通過一些Cron在線工具非常方便的生成逛绵。
點評
Spring Quartz 特點:【按照配置文件時間表達式:準時準點(不延時的時候)】 ---> 配置到spring application.xml上
- 默認多線程異步執(zhí)行
- 一個任務在上一次調(diào)度未完成執(zhí)行,下一次調(diào)度時間到時倔韭,會另起一個線程開始新的調(diào)度术浪。在業(yè)務繁忙時,一個任務或許會有多個線程在執(zhí)行寿酌,導致數(shù)據(jù)處理異常胰苏。
- 單任務同步:配置屬性,可以使一個任務的一次調(diào)度在未完成時醇疼,而不會開啟下一次調(diào)度
- 多個任務同時運行硕并,任務之間沒有直接的影響,多任務執(zhí)行的快慢取決于CPU的性能
- 一個類對應不一樣的job方法并可以定義為不一樣的job task任務 [看配置文件內(nèi)容]
推薦使用CronTrigger的觸發(fā)器的方式僵腺,因為該方式無需繼承任何父類或者實現(xiàn)任何的接口鲤孵,對原有功能代碼的侵入性比較小壶栋,而且可以實現(xiàn)指定時間執(zhí)行和指定時間間隔執(zhí)行辰如,使用上幾乎沒有任何的限制,因此往往在工程代碼中贵试,見到的更多的是這種方式實現(xiàn)的定時調(diào)度任務琉兜。