單機定時任務的四種基本實現(xiàn)方式

引言

在實際項目開發(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)度!

Quartz體系結(jié)構(gòu).png

特點

  • 強大的調(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>

定時任務

  1. 編寫測試定時任務測試代碼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();
    }

}
  1. 編寫測試定時任務測試代碼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上

    1. 默認多線程異步執(zhí)行
    1. 一個任務在上一次調(diào)度未完成執(zhí)行,下一次調(diào)度時間到時倔韭,會另起一個線程開始新的調(diào)度术浪。在業(yè)務繁忙時,一個任務或許會有多個線程在執(zhí)行寿酌,導致數(shù)據(jù)處理異常胰苏。
    1. 單任務同步:配置屬性,可以使一個任務的一次調(diào)度在未完成時醇疼,而不會開啟下一次調(diào)度
    1. 多個任務同時運行硕并,任務之間沒有直接的影響,多任務執(zhí)行的快慢取決于CPU的性能
    1. 一個類對應不一樣的job方法并可以定義為不一樣的job task任務 [看配置文件內(nèi)容]

推薦使用CronTrigger的觸發(fā)器的方式僵腺,因為該方式無需繼承任何父類或者實現(xiàn)任何的接口鲤孵,對原有功能代碼的侵入性比較小壶栋,而且可以實現(xiàn)指定時間執(zhí)行和指定時間間隔執(zhí)行辰如,使用上幾乎沒有任何的限制,因此往往在工程代碼中贵试,見到的更多的是這種方式實現(xiàn)的定時調(diào)度任務琉兜。

參考資料

  1. Spring配置Quartz定時任務
  2. Spring+Quartz整合(2)之SimpleTrigger
  3. # java定時任務實現(xiàn)的幾種方式(Timer、Spring Task毙玻、Quartz)
  4. Java定時任務調(diào)度詳解
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末豌蟋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子桑滩,更是在濱河造成了極大的恐慌梧疲,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異幌氮,居然都是意外死亡缭受,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門该互,熙熙樓的掌柜王于貴愁眉苦臉地迎上來米者,“玉大人,你說我怎么就攤上這事宇智÷悖” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵随橘,是天一觀的道長喂分。 經(jīng)常有香客問我,道長机蔗,這世上最難降的妖魔是什么妻顶? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蜒车,結(jié)果婚禮上讳嘱,老公的妹妹穿的比我還像新娘。我一直安慰自己酿愧,他們只是感情好沥潭,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嬉挡,像睡著了一般钝鸽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庞钢,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天拔恰,我揣著相機與錄音,去河邊找鬼基括。 笑死颜懊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的风皿。 我是一名探鬼主播河爹,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼桐款!你這毒婦竟也來了咸这?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤魔眨,失蹤者是張志新(化名)和其女友劉穎媳维,沒想到半個月后酿雪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡侄刽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年执虹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唠梨。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡袋励,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出当叭,到底是詐尸還是另有隱情茬故,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布蚁鳖,位于F島的核電站磺芭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏醉箕。R本人自食惡果不足惜钾腺,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讥裤。 院中可真熱鬧放棒,春花似錦、人聲如沸己英。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽损肛。三九已至厢破,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間治拿,已是汗流浹背摩泪。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劫谅,地道東北人见坑。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像同波,于是被迫代替她去往敵國和親鳄梅。 傳聞我的和親對象是個殘疾皇子叠国,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容