精华内容
下载资源
问答
  • 任务调度实现总结

    千次阅读 2018-08-16 11:53:35
    任务调度实现总结 前言 我们的应用程序有些定时任务(例如想在凌晨十二点半统计某个互联网金融公司一款借款APP前一天的借款、还款以及逾期情况)需要在指定时间内执行或者周期性执行某个任务(比如每月最后...

    任务调度的实现总结

    前言

     我们的应用程序有些定时任务(例如想在凌晨十二点半统计某个互联网金融公司一款借款APP前一天的借款、还款以及逾期情况)需要在指定时间内执行或者周期性执行某个任务(比如每月最后一天统计这个
     月的财务报表给财务部门等),这时候我们就需要用到任务调度框架了。Quartz正是一个炙手可热的任务调度框架,它简单易上手,并且可以与Spring集成(这才是重点)。
    
    
     我们可以使用任务调度器实现任务的定时执行。
    

    主要包含的目录概要

    JDK Timer & TimerTask的简单介绍和案例
        静态调用案例
        动态调用案例
    ScheduledExecutor的使用,和介绍
        线程池的创建四种方式的扩展
    任务调度框架Quartz的介绍和使用案例
    spring3 之后自带的定时任务以及实例
    
    

    常见的任务调度器简介

    1.JDK Timer & TimerTask

    Timer的核心是Timer和TimerTask,Timer负责设定TimerTask的起始与间隔执行时间,使用者需要建立一个timeTask的继承类,实现run方法,然后将其交给Timer使用即可。
    
    Timer的设计是一个TaskList和一个TaskThread。Timer将接到的任务交给TaskList中,TaskList按照最初执行时间进行排序。TimerThread在创建Timer会成为一个守护线程。这个线程会轮询任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread被唤醒并执行任务。之后Timer更新最近要执行的任务,继续休眠。
    
    缺点:所有任务都是串行的,前一个任务的延迟或异常会影响到后面的任务。
    
    
    总结的一些点:
    
    1.jdk中提供的一个定时器工具
    2.轻量级,知识简单的定时任务可以使用这个
    3.它只需要java.util.Timer和java.util.TimerTask两个类就可以实现基本任务调度功能
    4.只需实现TimerTask类即可使用Timer进行调度配置,使用起来简单方便
    5.Timer中所有的任务都一个TaskThread线程来调度和执行,任务的执行方式是串行的,如果前一个任务发生延迟或异常会影响到后续任务的执行
    6.TimerTask是一个实现了Runnable接口的抽象类,我是用TimerTask来创建一个任务,其中run方法里是任务调度的逻辑。使用一个Timer对象来调度任务
    7.Timer不保证任务执行的十分精确。
    8.每一个Timer仅对应唯一一个线程。
    9.Timer类的线程安全的。
    10.产生异常会终止调度
    

    注意:因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷

    2:JDK任务调度对JDKtimer Task的升级ScheduledExecutor

    注意

    ScheduledExecutorService(JDK1.5以后)替代Timer
    
    还有第一种的任务调度时,出现异常任务会结束,会影响到其他的任务进行
    
    因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷,所以我们可以采用线程池去解决第一种方式所留下的单线
    程的缺陷。
    
    只需要改变用线程池去执行任务,不需要改变任务做到什么

    3:任务调度框架Quartz

    作为一个优秀的开源调度框架,Quartz 具有以下特点: 
    1、强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求; 
    2、 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式; 
    3、分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。
    4.作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。
    5.Quartz专用词汇: 
        scheduler :任务调度器 
        trigger :触发器,用于定义任务调度时间规则 
        job :任务,即被调度的任务 
        misfire :错过的,指本来应该被执行但实际没有被执行的任务调度
    6.Quartz 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任务调度的元数据, scheduler 是实际执行调度的控制器
    7.trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。 
    8.Quartz 中主要提供了四种类型的 trigger: 
    SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger
    9.job 用于表示被调度的任务。两种类型:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次
        触发的任务被执行完之后,才能触发下一次执行,job默认无状态(无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰)
    10.Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger
        关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。job : trigger -> 1 : n
    11.scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。StdSchedulerFactory使用广泛。
        Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。
    12.“spring-context-support-3.2.4.RELEASE.jar” 此包是spring根据quartz中的主要类进行再次封装成具有bean风格的类;
        “quartz-2.2.1.jar” quartz的核心包
    

    4:spring3 之后自带的定时任务Spring-Task以及实例

    1.Spring3之后支持的一种任务调度器
    2.可以将它比作一个轻量级的Quartz
    3.使用起来很简单,除spring相关的包外不需要额外的包
    4.而且支持注解和配置文件两种
    5.配置相对少,配置trigger-触发器也和Quartz一样支持两种方式
    

    代码案例讲解

    1.JDK Timer & TimerTask

    代码步骤简要:

    1.自己的任务类要继承TimerTask类,然后实现run()方法,在run方法去定义自己的任务
    2.定义计时器,设置计时器的调度相关时间
    3.执行代码,开始进行调度

    代码概要:

    静态调用【执行周期在调用前设定好不可改变】

    package com.pkk.ehcache.task;
    
    import java.util.Random;
    import java.util.Timer;
    import java.util.TimerTask;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.pkk.ehcache.service.UserServcice;
    
    /**
     * @author peikunkun
     * @version V1.0
     * @Title: 不可动态修改的任务
     * @Package com.pkk.ehcache.task
     * @Description: <使用的是JDK本身自带的任务调度方式查询用户信息>
     * @date 2018/4/4 14:23
     */
    public class QueryUserForJdkTask extends TimerTask {
    
        //private static final long PERIOD = 5 * 60 * 1000;// 5分钟
        private static final long PERIOD = 1 * 1000;// 1秒钟
        @Autowired
        private UserServcice userServcice;
    
        /**
         * 执行次数
         */
        private int exeueCount = 0;
    
        /**
         * The action to be performed by this timer task.
         */
        @Override
        public void run() {
            int randomIn = new Random().nextInt(20);
            if (randomIn == 6) {
                randomIn = randomIn / 0;
            }
            System.out.println("任务调度,产生随机数为:" + randomIn);
        }
    
    
        public static void main(String[] args) {
            Timer timer = new Timer();
    
            QueryUserForJdkTask queryUserForJdkTask = new QueryUserForJdkTask();
    
            //延迟3秒在执行【单位为毫秒】
    //        timer.schedule(queryUserForJdkTask, 3000, PERIOD);
            timer.schedule(queryUserForJdkTask, 3000);
        }
    
    
    }
    

    Timer的方法特殊说明

            /**
             *time为Date类型:在指定时间执行一次(不周期)。
             */
            //timer.schedule(task, time);
            /**
             *firstTime为Date类型,period为long
             *从firstTime时刻开始,每隔period毫秒执行一次。
             */
            //timer.schedule(task, firstTime, period);
            /**
             *delay 为long类型:从现在起过delay毫秒之后执行一次(不周期)
             */
            //timer.schedule(task, delay);
            /**
             *delay为long,period为long:从现在起过delay毫秒以后,每隔period毫秒执行一次。
             */
            //timer.schedule(task, delay, period)

    动态改变代码调用例子【执行周期在调用后可一随意改变】

    任务

    package com.pkk.ehcache.task;
    
    import java.util.TimerTask;
    
    import com.pkk.ehcache.util.OutPutLoggerContext;
    
    /**
     * @author peikunkun
     * @version V1.0
     * @Title: frames
     * @Package com.pkk.ehcache.task
     * @Description: <>
     * @date 2018/4/4 18:40
     */
    public class DynamicTimerTask extends TimerTask {
    
        public static long exeCount = 0;
    
        /**
         * The action to be performed by this timer task.
         */
        @Override
        public void run() {
            exeCount++;
            OutPutLoggerContext.log("动态修改执行第" + exeCount + "次,获取的毫秒数为:" + System.currentTimeMillis());
        }
    }
    

    任务执行,启动,关闭,重启工具类

    package com.pkk.ehcache.task;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Timer;
    
    import com.pkk.ehcache.util.OutPutLoggerContext;
    
    /**
     * @author peikunkun
     * @version V1.0
     * @Title: frames
     * @Package com.pkk.ehcache.task
     * @Description: <如果关闭任务,stop方法完成了这个功能。要想任务不继续执行,
     * 必须将task的状态设置为cancel,然后调用timer的purge方法,将队列里的所有状态为cancel的task移除。这样就算到了执行时间,由于task已经移除,也就不会再执行了。如果使用了timer的cancel()方法,那么会将timer中所有的task全部移除掉。这点要注意一下。>
     * @date 2018/4/4 18:39
     */
    public class DynamicTaskManager {
    
        private static final long PERIOD = 2 * 1000;// 2秒钟
    
        private static DynamicTaskManager dynamicTaskManager = new DynamicTaskManager();
    
        private DynamicTaskManager() {
        }
    
        /**
         * 时间调度对象
         */
        private static Timer timer = new Timer();
    
        /**
         * 任务
         */
        private static DynamicTimerTask task = null;
    
    
        public static DynamicTaskManager getInstance() {
            if (dynamicTaskManager == null) {
                dynamicTaskManager = new DynamicTaskManager();
            }
            return dynamicTaskManager;
        }
    
    
        /**
         * 重新启动
         */
        public void restart() {
            clean();
            start();
        }
    
    
        /**
         * 重新启动
         */
        public void restart(Integer hour, Integer m, Integer s) {
            clean();
            start(hour, m, s);
        }
    
    
        /**
         * @param
         * @return void
         * @Description: <按一定的时间启动>
         * @author peikunkun
         * @date 2018/4/4 18:56
         * @version V1.0
         */
        public void start(Integer hour, Integer m, Integer s) {
    
            int isAnyNull = 0;
            Calendar calendar = Calendar.getInstance();
            if (hour != null && hour + "".length() >= 0) {
                calendar.set(Calendar.HOUR_OF_DAY, hour);
            } else {
                isAnyNull++;
            }
    
            if (m != null && m + "".length() >= 0) {
                calendar.set(Calendar.MINUTE, m);
            } else {
                isAnyNull++;
            }
    
            if (s != null && s + "".length() >= 0) {
                calendar.set(Calendar.SECOND, s);
            } else {
                isAnyNull++;
            }
    
            if (isAnyNull != 0) {
                start();
            } else {
                Date date = calendar.getTime();
                start(date, PERIOD);
            }
    
    
        }
    
    
        /**
         * 当前时刻启动
         */
        private void start() {
            start(new Date(), PERIOD);
        }
    
        /**
         * 启动定时器
         */
        public void start(Date startTime, long preiod) {
            startTask(startTime, preiod);
        }
    
        @SuppressWarnings("deprecation")
        public void startTask(Date startTime, long period) {
    
            OutPutLoggerContext.log("动态任务--启动任务:将在" + startTime.toLocaleString() + "后执行");
            //如果当前时间超过了设定时间,会立即执行一次
            task = new DynamicTimerTask();
            timer.schedule(task, startTime, period);
    
        }
    
        /**
         * 停止任务
         */
        public void stop() {
            OutPutLoggerContext.log("动态任务--正在进行关闭操作");
            clean();
            OutPutLoggerContext.log("动态任务--任务已经关闭成功");
        }
    
        /**
         * 清除任务
         */
        private void clean() {
            OutPutLoggerContext.log("动态任务--任务清除进行中");
            if (task != null) {
                //取消任务
                task.cancel();
            }
    
            //清除状态我已取消的任务
            timer.purge();
            DynamicTimerTask.exeCount = 0;
            OutPutLoggerContext.log("动态任务--任务清除完成");
        }
    
    
    }
    

    测试的方法

    @RequestMapping(value = "dynaQuartzTaskForJdk")
        public String dynaQuartzTaskForJdk(@RequestParam(value = "h", required = false) Integer h, @RequestParam(required = false, value = "m") Integer m, @RequestParam(required = false, value = "s") Integer s) {
            dynamicTaskManager.start(h, m, s);
            return "/ehcache/quartz";
        }
    
        @RequestMapping(value = "dynaCloseQuartzTaskForJdk")
        public String dynaCloseQuartzTaskForJdk() {
            dynamicTaskManager.stop();
            return "/ehcache/quartz";
        }
    
        @RequestMapping(value = "dynaRestartQuartzTaskForJdk")
        public String dynaRestartQuartzTaskForJdk(@RequestParam(value = "h", required = false) Integer h, @RequestParam(required = false, value = "m") Integer m, @RequestParam(required = false, value = "s") Integer s) {
            dynamicTaskManager.restart(h, m, s);
            return "/ehcache/quartz";
        }

    2.JDK ScheduledExecutor

    代码

    package com.pkk.ehcache.task;
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author peikunkun
     * @version V1.0
     * @Title: frames
     * @Package com.pkk.ehcache.task
     * @Description: <>
     * @date 2018/4/4 19:55
     */
    public class ScleduledExecutorTask implements Runnable {
        private String jobName = "";
    
        public ScleduledExecutorTask(String jobName) {
            this.jobName = jobName;
    
        }
    
        @Override
        public void run() {
            System.out.println("execute " + jobName);
        }
    
        public static void main(String[] args) {
            //申请个线程
            ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
            //设置了执行的名称,1秒延迟执行,1秒循环周期,单位是秒
            service.scheduleAtFixedRate(new ScleduledExecutorTask("jobName"), 1, 1, TimeUnit.SECONDS);
            service.scheduleWithFixedDelay(new ScleduledExecutorTask("jobName"), 1, 1, TimeUnit.SECONDS);
    
        }
    
    }
    

    其他线程池说明请点击此传送门

    注意:

    ScheduledExecutorService虽然解决了Timer由于单线程导致的问题,但从上述schedule方法可以看出它是基于延迟(initialDelay)来设定具体执行时间的,虽然可以通过计算实现某些复杂的作业
    调度配置,但这种用法过于繁杂而且执行时间不够明确

    3:任务调度框架Quartz

    quartz开源任务调度框架知识总结

    quartz 时间表达式之Cron表达式详解

    4:Spring Task 任务调度器

    xml配置方式案例

    config/spring/springTaskForXml.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
           default-lazy-init="true">
    
        <description>Spring Task ,这个是Spring本身自带的Task,步骤:1:注册任务bean,写任务做的事,2:开启任务调度,配置任务调度规则</description>
    
        <!--扫描注解包-->
        <context:component-scan base-package="com.pkk.ehcache"/>
    
    
        <!--注册bean,这里只是做一个演示,我使用的注解,不在此进行注入了-->
        <bean id="SpringXmlTaskOne" class="com.pkk.ehcache.task.SpringXmlTask"/>
        <!--<bean id="SpringXmlTaskTwo" class="com.pkk.ehcache.task.SpringXmlTaskTWO"/>-->
    
    
        <!--  开启任务调度  -->
        <!--任务调度的制定要执行的任务是谁,ref参数指定的即任务类,method,制定执行的任务的方法,下面是配置任务调度的两种方式-->
        <task:scheduled-tasks>
            <!--执行任务SpringXmlTaskOne,2妙后执行的方法是springXmlTaskOne,循环周期30秒-->
            <task:scheduled ref="SpringXmlTaskOne" method="springXmlTaskOne" initial-delay="2000" fixed-delay="30000"/>
            <!--执行任务SpringXmlTaskOne,2妙后执行的方法是springXmlTaskTwo,循环周期50秒-->
            <task:scheduled ref="SpringXmlTaskOne" method="springXmlTaskTwo" initial-delay="2000" fixed-delay="50000"/>
            <!--执行任务SpringXmlTaskOne,执行的方法是springXmlTaskThree,每年每月每日,每时每分的第六秒-->
            <task:scheduled ref="SpringXmlTaskOne" method="springXmlTaskThree" cron="6 * * * * ?"/>
        </task:scheduled-tasks>
    
        <!--知识特殊说明-->
        <!--ref是工作类
        method是工作类中要执行的方法
        initial-delay是任务第一次被调用前的延时,单位毫秒
        fixed-delay是上一个调用完成后再次调用的延时
        fixed-rate是上一个调用开始后再次调用的延时(不用等待上一次调用完成)
        cron是表达式,表示在什么时候进行任务调度。-->
    
    </beans>

    任务定义

    com.pkk.ehcache.task.SpringXmlTask

    package com.pkk.ehcache.task;
    
    import com.pkk.ehcache.constand.SysConstand;
    import com.pkk.ehcache.util.OutPutLoggerContext;
    import org.springframework.stereotype.Component;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @author kunzai
     * @time 2018年4月1日
     */
    @Component(value = "sPringXmlTask")
    public class SpringXmlTask {
    
    
        /**
         * @author kunzai
         * @version V1.0
         * @Title: springXmlTask
         * @Package com.pkk.ehcache.task
         * @Description: <Spring的Xml配置文件版的任务调度>
         * @date 18-4-5下午2:01
         */
        public void springXmlTaskOne() {
            OutPutLoggerContext.log("1:现在时刻是" + new SimpleDateFormat(SysConstand.FORMAT).format(new Date()) + "正在执行任务" + this.getClass().getSimpleName() + ".springXmlTaskOne(),获取的毫秒是:" + System.currentTimeMillis());
        }
    
        public void springXmlTaskTwo() {
            OutPutLoggerContext.log("2:现在时刻是" + new SimpleDateFormat(SysConstand.FORMAT).format(new Date()) + "正在执行任务" + this.getClass().getSimpleName() + ".springXmlTaskTwo(),获取的毫秒是:" + System.currentTimeMillis());
        }
    
        public void springXmlTaskThree() {
            OutPutLoggerContext.log("3:现在时刻是" + new SimpleDateFormat(SysConstand.FORMAT).format(new Date()) + "正在执行任务" + this.getClass().getSimpleName() + ".springXmlTaskThree(),获取的毫秒是:" + System.currentTimeMillis());
        }
    }
    

    注解配置方式案例

    config/spring/SpringTaskForAnnotation.xml

    开启注解配置任务调度器

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
    
        <!--可以在这配置扫描的包,我的是在其他的地方配置过了,在此不配置-->
        <!--<context:component-scan base-package="com.pkk.ehcache"/>-->
    
        <!--定义调度器,分配线程池大小为10-->
        <task:scheduler id="springAnnotationTaskScheduler" pool-size="10"/>
        <!--开启这个配置,spring才能识别@Scheduled注解,mode默认proxy-->
        <task:annotation-driven scheduler="springAnnotationTaskScheduler" mode="proxy"/>
    
    </beans>

    配置任务和配置调度器规则

    package com.pkk.ehcache.task;
    
    import com.pkk.ehcache.constand.SysConstand;
    import com.pkk.ehcache.util.OutPutLoggerContext;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import static java.lang.System.out;
    
    /**
     * @author kunzai
     * @version V1.0
     * @Title: SpringAnnotationTask
     * @Package com.pkk.ehcache.task
     * @Description: <>
     * @date 18-4-5下午3:30
     */
    @Component(value = "springAnnotationTask")
    public class SpringAnnotationTask {
    
        /**
         * @author kunzai
         * @version V1.0
         * @Title: springTaskAnnotationMethod
         * @Package com.pkk.ehcache.task
         * @Description: <任务的注解的形式---等待2妙之后,循环周期是40妙>
         * @date 18-4-5下午3:50
         */
        @Scheduled(initialDelay = 2000,fixedDelay = 1000*40)
        public void springTaskAnnotationMethodOne(){
            OutPutLoggerContext.log("4:【注解形势--简单方式】现在时刻是" + new SimpleDateFormat(SysConstand.FORMAT).format(new Date()) + "正在执行任务" + this.getClass().getSimpleName() + ".springTaskAnnotationMethodOne(),获取的毫秒是:" + System.currentTimeMillis());
        }
    
    
    
        /**
         * @author kunzai
         * @version V1.0
         * @Title: springTaskAnnotationMethod
         * @Package com.pkk.ehcache.task
         * @Description: <任务的注解的形式---每年每月每日,每时每分的第八秒执行>
         * @date 18-4-5下午3:50
         */
        @Scheduled(cron = "8 * * * * ?")
        public void springTaskAnnotationMethodTwo(){
            OutPutLoggerContext.log("5:【注解形势--cron方式】现在时刻是" + new SimpleDateFormat(SysConstand.FORMAT).format(new Date()) + "正在执行任务" + this.getClass().getSimpleName() + ".springTaskAnnotationMethodTwo(),获取的毫秒是:" + System.currentTimeMillis());
        }
    
    }
    展开全文
  • Spring任务调度介绍介绍下Spring的任务调度,启动一个间隔1秒的定时任务,首先开启Spring定时任务:import org.springframework.context.annotation.Configuration; import org.springframework.scheduling....

    一. Spring任务调度介绍

    介绍下Spring的任务调度,启动一个间隔1秒的定时任务,首先开启Spring定时任务:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @Configuration
    @EnableScheduling
    public class TimerConfig {
    
    }

    在@Configuration注解下添加@EnableScheduling就可以启动任务调度

    然后配置定时任务:

    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TimerTask {
    
        @Scheduled(fixedDelay = 2000)
        public void doTask() {
            System.out.println("task executing");
        }
    }

    这里设置2秒执行间隔

    最后启动Spring容器:

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class Main {
        public static void main(String[] args) throws InterruptedException {
            AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
            annotationConfigApplicationContext.scan("com.tubemogul.springsecuredemo.springDemo.basicTimer");
            annotationConfigApplicationContext.refresh();
        }
    }

    在控制台中可以看到定时任务被执行了:
    这里写图片描述

    但是现在有个新需求,要求动态启停任务,也就是在程序中启动/停止定时任务,当然比较简单的做法是在定时任务中加个bool变量,动态改变这个变量来控制是否执行这个任务,但如果定时任务很多,代码就很丑了,而且一堆空任务会无谓的消耗计算机资源。

    Spring调度器本身没有实现这个功能,下面我们就在Spring调度器的基础上,实现动态启停。

    二. Spring 任务调度原理分析

    在实现自己定制的任务调度器以前,先分析一下Spring任务调度器的原理。

    我们从第一个配置看起@EnableScheduling,这是这个注解的源码:
    这里写图片描述

    关键在@Import注解,这个注解导入了SchedulingConfiguration类:
    这里写图片描述

    这也是一个配置类,在这个类中导入了ScheduledAnnotationBeanPostProcessor进入Spring容器,从名字上就可以看出这个类是一个BeanPostprocessor,了解Spring容器原理可知,后处理器的postProcessBeforeInitialization(Object bean, String beanName)方法和postProcessAfterInitialization(Object bean, String beanName)方法在Bean创建后被调用(这两个方法调用时间有细微差别,前者在自定义初始化代码生效前调用,后者在自定义初始化代码生效后调用)

    有用的逻辑在postProcessBeforeInitialization(Object bean, String beanName)方法里面:
    这里写图片描述

    可以看出来实际就是扫描类的里面的方法是否包含了@Scheduled注解,然后调用processScheduled(scheduled, method, bean)注册调度任务,跟踪看看processScheduled(Scheduled scheduled, Method method, Object bean)方法,里面有一些对jdk代理和cglib代理的特殊处理,建立runnable对象,关键片段在这里:
    这里写图片描述

    现在去看看registrar对象是何方神圣
    这里写图片描述

    进入ScheduledTaskRegistrar类看看
    这里写图片描述

    可以看到实际所有的调度任务都被保存在这里。看看第一个变量private TaskScheduler taskScheduler; 阅读后面的源码可以知道,这个是真实的任务调度接口,这个接口有3个实现类:
    这里写图片描述

    从名字上看,我们先选择带线程池那个ThreadPoolTaskScheduler来看看:
    这里写图片描述

    终于找到了ScheduledExecutorService接口,原来Spring任务调度是基于ScheduledExecutorService实现的,这个是java.lang包里面提供的多线程调度工具类。

    三. 实现可动态启停的任务调度器

    前面已经分析了Spring调度器的原理,简单说就是定义一个BeanPostProcessor,扫描Bean方法中是否有任务调度的注解,如果有,则解析调度策略并则注册任务到TaskScheduler中进行调度。

    OK,那么要实现可动态启停的思路就是自己实现一个BeanPostProcessor,自定义一个注解来标记需要调度的任务,然后向TaskScheduler中注册,然后提供一个启停任务的manager类,话不多说,直接上代码。
    自定义一个任务标记注解MyFixDelaySchedule:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyFixDelaySchedule {
        long fixedDelay() default 60 * 60 * 1000;
    }
    

    这里只实现了fixedDelay,其他的cron表达式,fixedRate等调度方式可以参考源码自己添加

    修改一下TimerTask,使用自己的任务调度注解:

    import org.springframework.stereotype.Component;
    
    @Component
    public class TimerTask {
    
        @MyFixDelaySchedule(fixedDelay = 2000)
        public void doTask() {
            System.out.println("task executing");
        }
    }

    然后是MyScheduledAnnotationBeanPostProcessor,一个BeanPostProcessor

    import java.lang.reflect.Method;
    import java.util.Collections;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.springframework.aop.support.AopUtils;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.core.MethodIntrospector;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.scheduling.config.IntervalTask;
    import org.springframework.scheduling.support.ScheduledMethodRunnable;
    
    public class MyScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, DisposableBean {
    
        @Autowired
        private MySchedulingManager mySchedulingManager;
    
        private final Set<Class<?>> nonAnnotatedClasses = Collections
                .newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
    
        @Override
        public int getOrder() {
            return LOWEST_PRECEDENCE;
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            if (!this.nonAnnotatedClasses.contains(targetClass)) {
                Map<Method, Set<MyFixDelaySchedule>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                        new MethodIntrospector.MetadataLookup<Set<MyFixDelaySchedule>>() {
                            @Override
                            public Set<MyFixDelaySchedule> inspect(Method method) {
                                Set<MyFixDelaySchedule> scheduledMethods = AnnotationUtils.getRepeatableAnnotations(method,
                                        MyFixDelaySchedule.class, MyFixDelaySchedule.class);
                                return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
                            }
                        });
                if (annotatedMethods.isEmpty()) {
                    this.nonAnnotatedClasses.add(targetClass);
                } else {
                    // Non-empty set of methods
                    for (Map.Entry<Method, Set<MyFixDelaySchedule>> entry : annotatedMethods.entrySet()) {
                        Method method = entry.getKey();
                        for (MyFixDelaySchedule scheduled : entry.getValue()) {
                            Runnable runnable = new ScheduledMethodRunnable(bean, method);
                            IntervalTask task = new IntervalTask(runnable, scheduled.fixedDelay());
                            mySchedulingManager.addFixedDelayTask(task);
                        }
                    }
                }
            }
            return bean;
        }
    
        @Override
        public void destroy() throws Exception {
            mySchedulingManager.stopScheduleJobs();
        }
    
    }

    写完BeanPostProcess后需要一个配置类把这个BeanPostProcess导入Spring容器:

    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Role;
    
    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class MySchedulingConfiguration {
    
        @Bean
        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        public MyScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
            return new MyScheduledAnnotationBeanPostProcessor();
        }
    }

    最后是任务控制类:

    import java.util.ArrayList;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Set;
    import java.util.concurrent.ScheduledFuture;
    
    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
    import org.springframework.scheduling.config.IntervalTask;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MySchedulingManager {
        private ThreadPoolTaskScheduler taskScheduler;
        private List<IntervalTask> fixedDelayTasks = new ArrayList<IntervalTask>();
        private final Set<ScheduledFuture<?>> scheduledFutures = new LinkedHashSet<ScheduledFuture<?>>();
    
        public MySchedulingManager() {
            taskScheduler = new ThreadPoolTaskScheduler();
            taskScheduler.setPoolSize(4);
            taskScheduler.initialize();
            taskScheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true);
        }
    
        public void addFixedDelayTask(IntervalTask task) {
            this.fixedDelayTasks.add(task);
        }
    
        public synchronized void startScheduleJobs() {
            for (IntervalTask task : this.fixedDelayTasks) {
                this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
                        task.getRunnable(), task.getInterval()));
            }
        }
    
        public synchronized void stopScheduleJobs() {
            for (ScheduledFuture<?> future : this.scheduledFutures) {
                future.cancel(true);
            }
            scheduledFutures.clear();
        }
    }

    写Main类,先启动任务,运行10秒后停止任务,再等10秒再启动任务,代码如下:

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class Main {
        public static void main(String[] args) throws InterruptedException {
            AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
            annotationConfigApplicationContext.scan("com.tubemogul.springsecuredemo.springDemo.basicTimer");
            annotationConfigApplicationContext.refresh();
    
            MySchedulingManager mySchedulingManager = annotationConfigApplicationContext.getBean(MySchedulingManager.class);
            System.out.println("start job!");
            mySchedulingManager.startScheduleJobs();
            Thread.sleep(10000);
            System.out.println("stop job!");
            mySchedulingManager.stopScheduleJobs();
            Thread.sleep(10000);
            System.out.println("re-start job!");
            mySchedulingManager.startScheduleJobs();
        }
    }

    运行结果如下:
    这里写图片描述

    这样我们就实现了可动态启停的任务调度器。
    说明一点,Spring的任务调度器需要@EnableScheduling来生效是因为这个注解才能把SchedulingConfiguration导入Spring容器,而我自己实现这个MySchedulingConfiguration由于是放在com.tubemogul.springsecuredemo.springDemo.basicTimer包下面的,在执行annotationConfigApplicationContext.scan(“com.tubemogul.springsecuredemo.springDemo.basicTimer”);自动就会把MySchedulingConfiguration自动就被扫描进入Spring容器,所有就不需要类似@EnableScheduling的配置了

    展开全文
  • 4种任务调度java实现

    千次阅读 2015-08-04 09:53:16
    本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。 9 评论: 张 静, 软件...

    (转IBM)综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。

    前言

    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务调度的 Java 实现:

    • Timer
    • ScheduledExecutor
    • 开源工具包 Quartz
    • 开源工具包 JCronTab

    此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。

    Timer

    相信大家都已经非常熟悉 java.util.Timer 了,它是最简单的一种实现任务调度的方法,下面给出一个具体的例子:

    清单 1. 使用 Timer 进行任务调度
     package com.ibm.scheduler; 
     import java.util.Timer; 
     import java.util.TimerTask; 
    
     public class TimerTest extends TimerTask { 
    
     private String jobName = ""; 
    
     public TimerTest(String jobName) { 
     super(); 
     this.jobName = jobName; 
     } 
    
     @Override 
     public void run() { 
     System.out.println("execute " + jobName); 
     } 
    
     public static void main(String[] args) { 
     Timer timer = new Timer(); 
     long delay1 = 1 * 1000; 
     long period1 = 1000; 
     // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1 
     timer.schedule(new TimerTest("job1"), delay1, period1); 
     long delay2 = 2 * 1000; 
     long period2 = 2000; 
     // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2 
     timer.schedule(new TimerTest("job2"), delay2, period2); 
     } 
     } 
     Output: 
     execute job1 
     execute job1 
     execute job2 
     execute job1 
     execute job1 
     execute job2

    使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。

    使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。

    Timer 的设计核心是一个 TaskList 和一个 TaskThread。Timer 将接收到的任务丢到自己的 TaskList 中,TaskList 按照 Task 的最初执行时间进行排序。TimerThread 在创建 Timer 时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,

    然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠。

    Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,

    同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

    ScheduledExecutor

    鉴于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor。其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,

    因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

    清单 2. 使用 ScheduledExecutor 进行任务调度
     package com.ibm.scheduler;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ScheduledExecutorTest implements Runnable {
    	private String jobName = "";
    
    	public ScheduledExecutorTest(String jobName) {
    		super();
    		this.jobName = jobName;
    	}
    
    	@Override
    	public void run() {
    		System.out.println("execute " + jobName);
    	}
    
    	public static void main(String[] args) {
    		ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    
    		long initialDelay1 = 1;
    		long period1 = 1;
            // 从现在开始1秒钟之后,每隔1秒钟执行一次job1
    		service.scheduleAtFixedRate(
    		        new ScheduledExecutorTest("job1"), initialDelay1,
    				period1, TimeUnit.SECONDS);
    
    		long initialDelay2 = 1;
    		long delay2 = 1;
            // 从现在开始2秒钟之后,每隔2秒钟执行一次job2
    		service.scheduleWithFixedDelay(
    		        new ScheduledExecutorTest("job2"), initialDelay2,
    				delay2, TimeUnit.SECONDS);
    	}
    }
    Output:
    execute job1
    execute job1
    execute job2
    execute job1
    execute job1
    execute job2

    清单 2 展示了 ScheduledExecutorService 中两种最常用的调度方法 ScheduleAtFixedRate 和 ScheduleWithFixedDelay。

    ScheduleAtFixedRate 每次执行时间为上一次任务开始起向后推一个时间间隔,

    即每次执行时间为 :initialDelay, initialDelay+period, initialDelay+2*period, …;

    ScheduleWithFixedDelay 每次执行时间为上一次任务结束起向后推一个时间间隔,即每次执行时间为:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay。由此可见,ScheduleAtFixedRate 是基于固定时间间隔进行任务调度,

    ScheduleWithFixedDelay 取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度。


    用 ScheduledExecutor 和 Calendar 实现复杂任务调度

    Timer 和 ScheduledExecutor 都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求。比如,设置每星期二的 16:38:10 执行任务。该功能使用 Timer 和 ScheduledExecutor 都不能直接实现,但我们可以借助 Calendar 间接实现该功能。

    清单 3. 使用 ScheduledExcetuor 和 Calendar 进行任务调度
     package com.ibm.scheduler;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ScheduledExceutorTest2 extends TimerTask {
    
    	private String jobName = "";
    
    	public ScheduledExceutorTest2(String jobName) {
    		super();
    		this.jobName = jobName;
    	}
    
    	@Override
    	public void run() {
    		System.out.println("Date = "+new Date()+", execute " + jobName);
    	}
    
    	/**
    	 * 计算从当前时间currentDate开始,满足条件dayOfWeek, hourOfDay, 
    	 * minuteOfHour, secondOfMinite的最近时间
    	 * @return
    	 */
    	public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek,
    			int hourOfDay, int minuteOfHour, int secondOfMinite) {
    		//计算当前时间的WEEK_OF_YEAR,DAY_OF_WEEK, HOUR_OF_DAY, MINUTE,SECOND等各个字段值
    		int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR);
    		int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK);
    		int currentHour = currentDate.get(Calendar.HOUR_OF_DAY);
    		int currentMinute = currentDate.get(Calendar.MINUTE);
    		int currentSecond = currentDate.get(Calendar.SECOND);
    
    		//如果输入条件中的dayOfWeek小于当前日期的dayOfWeek,则WEEK_OF_YEAR需要推迟一周
    		boolean weekLater = false;
    		if (dayOfWeek < currentDayOfWeek) {
    			weekLater = true;
    		} else if (dayOfWeek == currentDayOfWeek) {
    			//当输入条件与当前日期的dayOfWeek相等时,如果输入条件中的
    			//hourOfDay小于当前日期的
    			//currentHour,则WEEK_OF_YEAR需要推迟一周	
    			if (hourOfDay < currentHour) {
    				weekLater = true;
    			} else if (hourOfDay == currentHour) {
                     //当输入条件与当前日期的dayOfWeek, hourOfDay相等时,
                     //如果输入条件中的minuteOfHour小于当前日期的
    				//currentMinute,则WEEK_OF_YEAR需要推迟一周
    				if (minuteOfHour < currentMinute) {
    					weekLater = true;
    				} else if (minuteOfHour == currentSecond) {
                         //当输入条件与当前日期的dayOfWeek, hourOfDay, 
                         //minuteOfHour相等时,如果输入条件中的
                        //secondOfMinite小于当前日期的currentSecond,
                        //则WEEK_OF_YEAR需要推迟一周
    					if (secondOfMinite < currentSecond) {
    						weekLater = true;
    					}
    				}
    			}
    		}
    		if (weekLater) {
    			//设置当前日期中的WEEK_OF_YEAR为当前周推迟一周
    			currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1);
    		}
    		// 设置当前日期中的DAY_OF_WEEK,HOUR_OF_DAY,MINUTE,SECOND为输入条件中的值。
    		currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek);
    		currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
    		currentDate.set(Calendar.MINUTE, minuteOfHour);
    		currentDate.set(Calendar.SECOND, secondOfMinite);
    		return currentDate;
    
    	}
    
    	public static void main(String[] args) throws Exception {
    
    		ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1");
    		//获取当前时间
    		Calendar currentDate = Calendar.getInstance();
    		long currentDateLong = currentDate.getTime().getTime();
    		System.out.println("Current Date = " + currentDate.getTime().toString());
    		//计算满足条件的最近一次执行时间
    		Calendar earliestDate = test
    				.getEarliestDate(currentDate, 3, 16, 38, 10);
    		long earliestDateLong = earliestDate.getTime().getTime();
    		System.out.println("Earliest Date = "
    				+ earliestDate.getTime().toString());
    		//计算从当前时间到最近一次执行时间的时间间隔
    		long delay = earliestDateLong - currentDateLong;
    		//计算执行周期为一星期
    		long period = 7 * 24 * 60 * 60 * 1000;
    		ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    		//从现在开始delay毫秒之后,每隔一星期执行一次job1
    		service.scheduleAtFixedRate(test, delay, period,
    				TimeUnit.MILLISECONDS);
    
    	}
    } 
    Output:
    Current Date = Wed Feb 02 17:32:01 CST 2011
    Earliest Date = Tue Feb 8 16:38:10 CST 2011
    Date = Tue Feb 8 16:38:10 CST 2011, execute job1
    Date = Tue Feb 15 16:38:10 CST 2011, execute job1

    清单 3 实现了每星期二 16:38:10 调度任务的功能。其核心在于根据当前时间推算出最近一个星期二 16:38:10 的绝对时间,

    然后计算与当前时间的时间差,作为调用 ScheduledExceutor 函数的参数。计算最近时间要用到 java.util.calendar 的功能。

    首先需要解释 calendar 的一些设计思想。Calendar 有以下几种唯一标识一个日期的组合方式:

     YEAR + MONTH + DAY_OF_MONTH 
     YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK 
     YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK 
     YEAR + DAY_OF_YEAR 
     YEAR + DAY_OF_WEEK + WEEK_OF_YEAR

    上述组合分别加上 HOUR_OF_DAY + MINUTE + SECOND 即为一个完整的时间标识。本例采用了最后一种组合方式。

    输入为 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 以及当前日期 , 

    输出为一个满足 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 并且距离当前日期最近的未来日期。

    计算的原则是从输入的 DAY_OF_WEEK 开始比较,如果小于当前日期的 DAY_OF_WEEK,则需要向 WEEK_OF_YEAR 进一, 

    即将当前日期中的 WEEK_OF_YEAR 加一并覆盖旧值;如果等于当前的 DAY_OF_WEEK, 则继续比较 HOUR_OF_DAY;

    如果大于当前的 DAY_OF_WEEK,则直接调用 java.util.calenda 的 calendar.set(field, value) 函数将当前日期的 DAY_OF_WEEK, HOUR_OF_DAY, 

    MINUTE, SECOND 赋值为输入值,依次类推,直到比较至 SECOND。读者可以根据输入需求选择不同的组合方式来计算最近执行时间。

    可以看出,用上述方法实现该任务调度比较麻烦,这就需要一个更加完善的任务调度框架来解决这些复杂的调度问题。

    幸运的是,开源工具包 Quartz 与 JCronTab 提供了这方面强大的支持。


    Quartz

    Quartz 可以满足更多更复杂的调度需求,首先让我们看看如何用 Quartz 实现每星期二 16:38 的调度安排:

    清单 4. 使用 Quartz 进行任务调度
     package com.ibm.scheduler;
    import java.util.Date;
    
    import org.quartz.Job;
    import org.quartz.JobDetail;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.helpers.TriggerUtils;
    
    public class QuartzTest implements Job {
    
    	@Override
    	//该方法实现需要执行的任务
    	public void execute(JobExecutionContext arg0) throws JobExecutionException {
    		System.out.println("Generating report - "
    				+ arg0.getJobDetail().getFullName() + ", type ="
    				+ arg0.getJobDetail().getJobDataMap().get("type"));
    		System.out.println(new Date().toString());
    	}
    	public static void main(String[] args) {
    		try {
    			// 创建一个Scheduler
    			SchedulerFactory schedFact = 
    			new org.quartz.impl.StdSchedulerFactory();
    			Scheduler sched = schedFact.getScheduler();
    			sched.start();
    			// 创建一个JobDetail,指明name,groupname,以及具体的Job类名,
    			//该Job负责定义需要执行任务
    			JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",
    					QuartzTest.class);
    			jobDetail.getJobDataMap().put("type", "FULL");
                // 创建一个每周触发的Trigger,指明星期几几点几分执行
    			Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);
    			trigger.setGroup("myTriggerGroup");
    			// 从当前时间的下一秒开始执行
    			trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));
    			// 指明trigger的name
    			trigger.setName("myTrigger");
    			// 用scheduler将JobDetail与Trigger关联在一起,开始调度任务
    			sched.scheduleJob(jobDetail, trigger);
    
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    } 
    Output:
    Generating report - myJobGroup.myJob, type =FULL
    Tue Feb 8 16:38:00 CST 2011
    Generating report - myJobGroup.myJob, type =FULL
    Tue Feb 15 16:38:00 CST 2011

    清单 4 非常简洁地实现了一个上述复杂的任务调度。Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。

    其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。

    Job

    使用者只需要创建一个 Job 的继承类,实现 execute 方法。JobDetail 负责封装 Job 以及 Job 的属性,并将其提供给 Scheduler 作为参数。

    每次 Scheduler 执行任务时,首先会创建一个 Job 的实例,然后再调用 execute 方法执行。

    Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。

    JobDataMap 可以存储任意数量的 Key,Value 对,例如:

    清单 5. 为 JobDataMap 赋值
     jobDetail.getJobDataMap().put("myDescription", "my job description"); 
     jobDetail.getJobDataMap().put("myValue", 1998); 
     ArrayList<String> list = new ArrayList<String>(); 
     list.add("item1"); 
     jobDetail.getJobDataMap().put("myArray", list);

    JobDataMap 中的数据可以通过下面的方式获取:

    清单 6. 获取 JobDataMap 的值
     public class JobDataMapTest implements Job {
    
    	@Override
    	public void execute(JobExecutionContext context)
    			throws JobExecutionException {
    		//从context中获取instName,groupName以及dataMap
    		String instName = context.getJobDetail().getName();
    		String groupName = context.getJobDetail().getGroup();
    		JobDataMap dataMap = context.getJobDetail().getJobDataMap();
    		//从dataMap中获取myDescription,myValue以及myArray
    		String myDescription = dataMap.getString("myDescription");
    		int myValue = dataMap.getInt("myValue");
    		ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray");
    		System.out.println("
    		        Instance =" + instName + ", group = " + groupName
                    + ", description = " + myDescription + ", value =" + myValue
                    + ", array item0 = " + myArray.get(0));
    
    	}
    }
    Output:
    Instance = myJob, group = myJobGroup, 
    description = my job description, 
    value =1998, array item0 = item1

    Trigger

    Trigger 的作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。

    SimpleTrigger 适用于在某一特定的时间执行一次,或者在某一特定的时间以某一特定时间间隔执行多次。

    上述功能决定了 SimpleTrigger 的参数包括 start-time, end-time, repeat count, 以及 repeat interval。

    Repeat count 取值为大于或等于零的整数,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。

    Repeat interval 取值为大于或等于零的长整型。当 Repeat interval 取值为零并且 Repeat count 取值大于零时,将会触发任务的并发执行。

    Start-time 与 dnd-time 取值为 java.util.Date。当同时指定 end-time 与 repeat count 时,优先考虑 end-time。

    一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY。

    以下是 SimpleTrigger 的构造方法:

     public SimpleTrigger(String name, 
                           String group, 
                           Date startTime, 
                           Date endTime, 
                           int repeatCount, 
                           long repeatInterval)

    举例如下:

    创建一个立即执行且仅执行一次的 SimpleTrigger:

     SimpleTrigger trigger=
     new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);

    创建一个半分钟后开始执行,且每隔一分钟重复执行一次的 SimpleTrigger:

     SimpleTrigger trigger=
     new SimpleTrigger("myTrigger", "myGroup", 
        new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);

    创建一个 2011 年 6 月 1 日 8:30 开始执行,每隔一小时执行一次,一共执行一百次,一天之后截止的 SimpleTrigger:

     Calendar calendar = Calendar.getInstance(); 
     calendar.set(Calendar.YEAR, 2011); 
     calendar.set(Calendar.MONTH, Calendar.JUNE); 
     calendar.set(Calendar.DAY_OF_MONTH, 1); 
     calendar.set(Calendar.HOUR, 8); 
     calendar.set(Calendar.MINUTE, 30); 
     calendar.set(Calendar.SECOND, 0); 
     calendar.set(Calendar.MILLISECOND, 0); 
     Date startTime = calendar.getTime(); 
     Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000); 
     SimpleTrigger trigger=new SimpleTrigger("myTrigger", 
            "myGroup", startTime, endTime, 100, 60*60*1000);

    上述最后一个例子中,同时设置了 end-time 与 repeat count,则优先考虑 end-time,总共可以执行二十四次。

    CronTrigger 的用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,CronTrigger 主要适用于基于日历的调度安排。

    例如:每星期二的 16:38:10 执行,每月一号执行,以及更复杂的调度安排等。

    CronTrigger 同样需要指定 start-time 和 end-time,其核心在于 Cron 表达式,由七个字段组成:

     Seconds 
     Minutes 
     Hours 
     Day-of-Month 
     Month 
     Day-of-Week 
     Year (Optional field)

    举例如下:

    创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:

     0 0 0/3  * * ?

    创建一个每十分钟执行的 CronTrigger,且从每小时的第三分钟开始执行:

     0 3/10 * * * ?

    创建一个每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时执行一次的 CronTrigger:

     0 0/30 20-23 ? * MON-WED,SAT

    创建一个每月最后一个周四,中午 11:30-14:30,每小时执行一次的 trigger:

     0 30 11-14/1 ? * 5L

    解释一下上述例子中各符号的含义:

    首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值为 0 到 59,Hours 取值为 0 到 23,

    Day-of-Month 取值为 0-31, Month 取值为 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, 

    SEP, OCT, NOV, DEC,Days-of-Week 取值为 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。

    每个字段可以取单个值,多个值,或一个范围,例如 Day-of-Week 可取值为“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。

    通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。

    / 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。

    ? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。适用于需要对这两个字段中的其中一个指定值,

    而对另一个不指定值的情况。一般情况下,这两个字段只需对一个赋值。

    L 仅适用于 Day-of-Month 和 Day-of-Week。L 用于 Day-of-Month 表示该月最后一天。L 单独用于 Day-of-Week 表示周六,

    否则表示一个月最后一个星期几,例如 5L 或者 THUL 表示该月最后一个星期四。

    W 仅适用于 Day-of-Month,表示离指定日期最近的一个工作日,例如 Day-of-Month 赋值为 10W 表示该月离 10 号最近的一个工作日。

    # 仅适用于 Day-of-Week,表示该月第 XXX 个星期几。例如 Day-of-Week 赋值为 5#2 或者 THU#2,表示该月第二个星期四。

    CronTrigger 的使用如下:

     CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup"); 
     try { 
         cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT"); 
     } catch (Exception e) { 
         e.printStackTrace(); 
     }

    Job 与 Trigger 的松耦合设计是 Quartz 的一大特点,其优点在于同一个 Job 可以绑定多个不同的 Trigger,

    同一个 Trigger 也可以调度多个 Job,灵活性很强。

    Listener

    除了上述基本的调度功能,Quartz 还提供了 listener 的功能。主要包含三种 listener:JobListener,

    TriggerListener 以及 SchedulerListener。当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。最常见的情况是,

    当任务被执行时,系统发生故障,Listener 监听到错误,立即发送邮件给管理员。下面给出 JobListener 的实例:

    清单 7. JobListener 的实现
     import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.JobListener;
    import org.quartz.SchedulerException;
    
    
    public class MyListener implements JobListener{
    
    	@Override
    	public String getName() {
    		return "My Listener";
    	}
    	@Override
    	public void jobWasExecuted(JobExecutionContext context,
    			JobExecutionException jobException) {
    		if(jobException != null){
    			try {
    				//停止Scheduler
    				context.getScheduler().shutdown();
    				System.out.println("
                    Error occurs when executing jobs, shut down the scheduler ");
                    // 给管理员发送邮件…
    			} catch (SchedulerException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

    从清单 7 可以看出,使用者只需要创建一个 JobListener 的继承类,重载需要触发的方法即可。

    当然,需要将 listener 的实现类注册到 Scheduler 和 JobDetail 中:

     sched.addJobListener(new MyListener()); 
     jobDetail.addJobListener("My Listener"); // listener 的名字

    使用者也可以将 listener 注册为全局 listener,这样便可以监听 scheduler 中注册的所有任务 :

     sched.addGlobalJobListener(new MyListener());

    为了测试 listener 的功能,可以在 job 的 execute 方法中强制抛出异常。清单 7 中,listener 接收到异常,

    将 job 所在的 scheduler 停掉,阻止后续的 job 继续执行。scheduler、jobDetail 等信息都可以从 listener 的参数 context 中检索到。

    清单 7 的输出结果为:

     Generating report - myJob.myJob, type =FULL 
     Tue Feb 15 18:57:35 CST 2011 
     2011-2-15 18:57:35 org.quartz.core.JobRunShell run 
    信息 : Job myJob.myJob threw a JobExecutionException: 
     org.quartz.JobExecutionException 
     at com.ibm.scheduler.QuartzListenerTest.execute(QuartzListenerTest.java:22) 
     at org.quartz.core.JobRunShell.run(JobRunShell.java:191) 
     at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:516) 
     2011-2-15 18:57:35 org.quartz.core.QuartzScheduler shutdown 
    信息 : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down. 
     Error occurs when executing jobs, shut down the scheduler

    TriggerListener、SchedulerListener 与 JobListener 有类似的功能,只是各自触发的事件不同,如 JobListener 触发的事件为:

    Job to be executed, Job has completed execution 等

    TriggerListener 触发的事件为:

    Trigger firings, trigger mis-firings, trigger completions 等

    SchedulerListener 触发的事件为:

    add a job/trigger, remove a job/trigger, shutdown a scheduler 等

    读者可以根据自己的需求重载相应的事件。

    JobStores

    Quartz 的另一显著优点在于持久化,即将任务调度的相关数据保存下来。这样,当系统重启后,

    任务被调度的状态依然存在于系统中,不会丢失。默认情况下,Quartz 采用的是 org.quartz.simpl.RAMJobStore,

    在这种情况下,数据仅能保存在内存中,系统重启后会全部丢失。若想持久化数据,需要采用 org.quartz.simpl.JDBCJobStoreTX。

    实现持久化的第一步,是要创建 Quartz 持久化所需要的表格。在 Quartz 的发布包 docs/dbTables 中可以找到相应的表格创建脚本。

    Quartz 支持目前大部分流行的数据库。本文以 DB2 为例,所需要的脚本为 tables_db2.sql。首先需要对脚本做一点小的修改,即在开头指明 Schema:

     SET CURRENT SCHEMA quartz;

    为了方便重复使用 , 创建表格前首先删除之前的表格:
    drop table qrtz_job_details;

     drop table qrtz_job_listeners;

    然后创建数据库 sched,执行 tables_db2.sql 创建持久化所需要的表格。

    第二步,配置数据源。数据源与其它所有配置,例如 ThreadPool,均放在 quartz.properties 里:

    清单 8. Quartz 配置文件
     # Configure ThreadPool 
     org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool 
     org.quartz.threadPool.threadCount =  5 
     org.quartz.threadPool.threadPriority = 4 
    
     # Configure Datasources 
     org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 
     org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate 
     org.quartz.jobStore.dataSource = db2DS 
     org.quartz.jobStore.tablePrefix = QRTZ_ 
    
     org.quartz.dataSource.db2DS.driver = com.ibm.db2.jcc.DB2Driver 
     org.quartz.dataSource.db2DS.URL = jdbc:db2://localhost:50001/sched 
     org.quartz.dataSource.db2DS.user = quartz 
     org.quartz.dataSource.db2DS.password = passw0rd 
     org.quartz.dataSource.db2DS.maxConnections = 5

    使用时只需要将 quatz.properties 放在 classpath 下面,不用更改一行代码,再次运行之前的任务调度实例,trigger、job 等信息便会被记录在数据库中。

    将清单 4 中的 makeWeeklyTrigger 改成 makeSecondlyTrigger,重新运行 main 函数,

    在 sched 数据库中查询表 qrtz_simple_triggers 中的数据。其查询语句为“db2 ‘ select repeat_interval, times_triggered from qrtz_simple_triggers ’”。

    结果 repeat_interval 为 1000,与程序中设置的 makeSecondlyTrigger 相吻合,times_triggered 值为 21。

    停掉程序,将数据库中记录的任务调度数据重新导入程序运行:

    清单 9. 从数据库中导入任务调度数据重新运行
     package com.ibm.scheduler; 
     import org.quartz.Scheduler; 
     import org.quartz.SchedulerException; 
     import org.quartz.SchedulerFactory; 
     import org.quartz.Trigger; 
     import org.quartz.impl.StdSchedulerFactory; 
    
     public class QuartzReschedulerTest { 
     public static void main(String[] args) throws SchedulerException { 
     // 初始化一个 Schedule Factory 
     SchedulerFactory schedulerFactory = new StdSchedulerFactory(); 
     // 从 schedule factory 中获取 scheduler 
     Scheduler scheduler = schedulerFactory.getScheduler(); 
     // 从 schedule factory 中获取 trigger 
     Trigger trigger = scheduler.getTrigger("myTrigger", "myTriggerGroup"); 
     // 重新开启调度任务
     scheduler.rescheduleJob("myTrigger", "myTriggerGroup", trigger); 
     scheduler.start(); 
     } 
     }

    清单 9 中,schedulerFactory.getScheduler() 将 quartz.properties 的内容加载到内存,然后根据数据源的属性初始化数据库的链接,

    并将数据库中存储的数据加载到内存。之后,便可以在内存中查询某一具体的 trigger,并将其重新启动。

    这时候重新查询 qrtz_simple_triggers 中的数据,发现 times_triggered 值比原来增长了。

    JCronTab

    习惯使用 unix/linux 的开发人员应该对 crontab 都不陌生。Crontab 是一个非常方便的用于 unix/linux 系统的任务调度命令。

    JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。

    首先简单介绍一下 crontab 的语法,与上面介绍的 Quartz 非常相似,但更加简洁 , 集中了最常用的语法。

    主要由六个字段组成(括弧中标识了每个字段的取值范围):

     Minutes (0-59)
     Hours   (0-23) 
     Day-of-Month (1-31)
     Month    (1-12/JAN-DEC) 
     Day-of-Week  (0-6/SUN-SAT)
     Command

    与 Quartz 相比,省略了 Seconds 与 Year,多了一个 command 字段,即为将要被调度的命令。

    JCronTab 中也包含符号“*”与“/”, 其含义与 Quartz 相同。

    举例如下:

    每天 12 点到 15 点 , 每隔 1 小时执行一次 Date 命令:

     0 12-15/1 * * * Date

    每月 2 号凌晨 1 点发一封信给 zhjingbj@cn.ibm.com:

     0 1 2 * * mail -s “good” zhjingbj@cn.ibm.com

    每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时打印“normal”:

     0/30 20-23 * * MON-WED,SAT echo “normal”

    JCronTab 借鉴了 crontab 的语法,其区别在于 command 不再是 unix/linux 的命令,而是一个 Java 类。如果该类带参数,例如“com.ibm.scheduler.JCronTask2#run”,则定期执行 run 方法;如果该类不带参数,则默认执行 main 方法。

    此外,还可以传参数给 main 方法或者构造函数,例如“com.ibm.scheduler.JCronTask2#run Hello World“表示传两个参数 Hello 和 World 给构造函数。

    JCronTab 与 Quartz 相比,其优点在于,第一,支持多种任务调度的持久化方法,包括普通文件、

    数据库以及 XML 文件进行持久化;第二,JCronTab 能够非常方便地与 Web 应用服务器相结合,

    任务调度可以随 Web 应用服务器的启动自动启动;第三,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。

    JCronTab 与 Web 应用服务器的结合非常简单,只需要在 Web 应用程序的 web.xml 中添加如下行:

    清单 10. 在 web.xml 中配置 JCronTab 的属性
     <servlet> 
       <servlet-name>LoadOnStartupServlet</servlet-name> 
       <servlet-class>org.jcrontab.web.loadCrontabServlet</servlet-class> 
       <init-param> 
     <param-name>PROPERTIES_FILE</param-name> 
     <param-value>D:/Scheduler/src/jcrontab.properties</param-value> 
       </init-param> 
       <load-on-startup>1</load-on-startup> 
     </servlet> 
     <!-- Mapping of the StartUp Servlet --> 
     <servlet-mapping> 
       <servlet-name>LoadOnStartupServlet</servlet-name> 
     <url-pattern>/Startup</url-pattern> 
     </servlet-mapping>

    在清单 10 中,需要注意两点:第一,必须指定 servlet-class 为 org.jcrontab.web.loadCrontabServlet,

    因为它是整个任务调度的入口;第二,必须指定一个参数为 PROPERTIES_FILE,才能被 loadCrontabServlet 识别。

    接下来,需要撰写 D:/Scheduler/src/jcrontab.properties 的内容,其内容根据需求的不同而改变。

    当采用普通文件持久化时,jcrontab.properties 的内容主要包括:

     org.jcrontab.data.file = D:/Scheduler/src/crontab 
     org.jcrontab.data.datasource = org.jcrontab.data.FileSource

    其中数据来源 org.jcrontab.data.datasource 被描述为普通文件,即 org.jcrontab.data.FileSource。

    具体的文件即 org.jcrontab.data.file 指明为 D:/Scheduler/src/crontab。

    Crontab 描述了任务的调度安排:

     */2 * * * * com.ibm.scheduler.JCronTask1 
     * * * * * com.ibm.scheduler.JCronTask2#run Hello World

    其中包含了两条任务的调度,分别是每两分钟执行一次 JCronTask1 的 main 方法,每一分钟执行一次 JCronTask2 的 run 方法。

    清单 11. JcronTask1 与 JCronTask2 的实现
     package com.ibm.scheduler;
    
    import java.util.Date;
    
    public class JCronTask1 {
    
    	private static int count = 0;
    
    	public static void main(String[] args) {
    		System.out.println("--------------Task1-----------------");
    		System.out.println("Current Time = " + new Date() + ", Count = "
    				+ count++);
    	}
    }
    
    package com.ibm.scheduler;
    
    import java.util.Date;
    
    public class JCronTask2 implements Runnable {
    
    	private static int count = 0;
    
    	private static String[] args;
    
    	public JCronTask2(String[] args) {
    		System.out.println("--------------Task2-----------------");
    		System.out.println("Current Time = " + new Date() + ", Count = "
    				+ count++);
    		JCronTask2.args = args;
    	}
    
    	@Override
    	public void run() {
    		System.out.println("enter into run method");
    		if (args != null && args.length > 0) {
    			for (int i = 0; i < args.length; i++) {
             System.out.print("This is arg " + i + " " + args[i] + "\n");
    			}
    		}
    	}
    }

    到此为止,基于普通文件持久化的 JCronTab 的实例就全部配置好了。启动 Web 应用服务器,便可以看到任务调度的输出结果:

     --------------Task2----------------- 
     Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0 
     enter into run method 
     This is arg 0 Hello 
     This is arg 1 World 
     --------------Task1----------------- 
     Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0 
     --------------Task2----------------- 
     Current Time = Tue Feb 15 09:23:00 CST 2011, Count = 1 
     enter into run method 
     This is arg 0 Hello 
     This is arg 1 World 
     --------------Task2----------------- 
     Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 2 
     enter into run method 
     This is arg 0 Hello 
     This is arg 1 World 
     --------------Task1----------------- 
     Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 1

    通过修改 jcrontab.properties 中 datasource,可以选择采用数据库或 xml 文件持久化,感兴趣的读者可以参考 进阶学习 JCronTab

    此外,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。其配置非常简单,

    只需要在 jcontab.properties 中添加几行配置即可:

     org.jcrontab.sendMail.to= Ther email you want to send to 
     org.jcrontab.sendMail.from=The email you want to send from 
     org.jcrontab.sendMail.smtp.host=smtp server 
     org.jcrontab.sendMail.smtp.user=smtp username 
     org.jcrontab.sendMail.smtp.password=smtp password

    结束语

    本文介绍了四种常用的对任务进行调度的 Java 实现方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。

    文本对每种方法都进行了实例解释,并对其优缺点进行比较。对于简单的基于起始时间点与时间间隔的任务调度,

    使用 Timer 就足够了;如果需要同时调度多个任务,基于线程池的 ScheduledTimer 是更为合适的选择;

    当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz 与 JCronTab 则体现出它们的优势。

    熟悉 Unix/Linux 的开发人员更倾向于 JCronTab,且 JCronTab 更适合与 Web 应用服务器相结合。

    Quartz 的 Trigger 与 Job 松耦合设计使其更适用于 Job 与 Trigger 的多对多应用场景。

    展开全文
  • java计划任务调度框架quartz结合spring实现调度的配置实例代码分享,代码下载地址:http://www.zuidaima.com/share/1755429240540160.htm

    原创不易,转载请注明出处:
    java计划任务调度框架quartz结合spring实现调度的配置实例代码分享

    代码下载地址:http://www.zuidaima.com/share/1755429240540160.htm

    一:quartz简介

           OpenSymphony 的Quartz提供了一个比较完美的任务调度解决方案。

           Quartz 是个开源的作业调度框架,定时调度器,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

           Quartz中有两个基本概念:作业和触发器。作业是能够调度的可执行任务,触发器提供了对作业的调度

    二:quartz spring配置详解

    •  为什么不适用java.util.Timer结合java.util.TimerTask 

            1.主要的原因,适用不方便,特别是制定具体的年月日时分的时间,而quartz使用类似linux上的cron配置,很方便的配置每隔时间执行触发。

            2.其次性能的原因,使用jdk自带的Timer不具备多线程,而quartz采用线程池,性能上比timer高出很多。
     

    •    详解quartz在spring里面的配置

        在spring里主要分为两种使用方式:第一种,也是目前使用最多的方式,spring提供的MethodInvokingJobDetailFactoryBean代理类,通过雷利类直接调用任务类的某个函数;第二种,程序里实现quartz接口,quartz通过该接口进行调度。

          主要讲解通过spring提供的代理类MethodInvokingJobDetailFactoryBean

            1.业务逻辑类:业务逻辑是独立的,本身就与quartz解耦的,并没有深入进去,这对业务来讲是很好的一个方式。

    public class  TestJobTask{
          /**
           *业务逻辑处理
           */
            public void   service(){
                /**业务逻辑*/
                    ..
            }
    }

           
        2.增加一个线程池

    <!-- 线程执行器配置,用于任务注册 -->
    <bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
     <property name="corePoolSize" value="10" />
     <property name="maxPoolSize" value="100" />
     <property name="queueCapacity" value="500" />
    </bean>

      3.定义业务逻辑类

    <!-- 业务对象 -->
    <bean id="testJobTask" class="com.mike.scheduling.TestJobTask" />

        4.增加quartz调用业务逻辑
     

    <!-- 调度业务 -->
    <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
     <property name="targetObject" ref="testJobTask" />
     <property name="targetMethod" value="service" />
    </bean>


        5.增加调用的触发器,触发的时间,有两种方式:

          第一种触发时间,采用类似linux的cron,配置时间的表示发出丰富  

    <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
     <property name="jobDetail" ref="jobDetail" />
     <property name="cronExpression" value="10 0/1 * * * ?" />
    </bean>

       Cron表达式“10 */1 * * * ?”意为:从10秒开始,每1分钟执行一次 
      
        第二种,采用比较简话的方式,申明延迟时间和间隔时间

    <bean id="taskTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
     <property name="jobDetail" ref="jobDetail" />
     <property name="startDelay" value="10000" />
     <property name="repeatInterval" value="60000" />
    </bean>

       延迟10秒启动,然后每隔1分钟执行一次 

        6.开始调用
     

    <!-- 设置调度 -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
     <property name="triggers">
      <list>
       <ref bean="cronTrigger" />
      </list>
     </property>
     <property name="taskExecutor" ref="executor" />
    </bean>


       7.结束:启动容器即可,已经将spring和quartz结合完毕。

       
    Cron常用的表达式


    "0 0 12 * * ?" 每天中午12点触发

    "0 15 10 ? * *" 每天上午10:15触发

    "0 15 10 * * ?" 每天上午10:15触发
    "0 15 10 * * ? *" 每天上午10:15触发
    "0 15 10 * * ? 2005" 2005年的每天上午10:15触发
    "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
    "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
    "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
    "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
    "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
    "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
    "0 15 10 15 * ?" 每月15日上午10:15触发
    "0 15 10 L * ?" 每月最后一日的上午10:15触发
    "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 
    "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
    "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发


    三:quartz原理

        根据上面spring的配置,我们就比较清楚quartz的内部情况,下面我们主要详解配置涉及到的每个点

        1.我们先从最后一个步骤看起, SchedulerFactoryBean ,scheduler的工厂实现,里面可以生产出对应的多个jobDetail和trigger,每个jobDetail对应trigger代表一个任务
              Quartz的SchedulerFactory是标准的工厂类,不太适合在Spring环境下使用。此外,为了保证Scheduler能够感知 Spring容器的生命周期,完成自动启动和关闭的操作,必须让Scheduler和Spring容器的生命周期相关联。以便在Spring容器启动后, Scheduler自动开始工作,而在Spring容器关闭前,自动关闭Scheduler。为此,Spring提供 SchedulerFactoryBean,这个FactoryBean大致拥有以下的功能: 

         1)以更具Bean风格的方式为Scheduler提供配置信息; 

         2)让Scheduler和Spring容器的生命周期建立关联,相生相息; 

         3)通过属性配置部分或全部代替Quartz自身的配置文件。 

      2.jobDetail,表示一个可执行的业务调用
      
      3.trigger:调度的时间计划,什么时候,每隔多少时间可执行等时间计划

      4. ThreadPoolTaskExecutor,线程池,用来并行执行每个对应的job,提高效率,这也是上面提到不推荐使用jdk自身timer的一个很重要的原因

    展开全文
  • os任务调度实现原理

    千次阅读 2019-07-06 21:07:17
    -what怎样实现任务调度?-how 为什么要做任务调度-why 操作系统中最为显著的特性就是任务调度任务调度主要来自于以下几种需求: 程序并发(multiprogram) 任务间同步、消息传递 实时性能要求 其中...
  • 分布式任务调度实现

    千次阅读 2018-04-12 11:45:13
    单机定式任务调度的问题在很多应用系统中我们常常要定时执行一些任务。比如,订单系统的超时状态判断、缓存数据的定时更新、定式给用户发邮件,甚至是一些定期计算的报表等等。常见的处理方式有线程的while(true) 和...
  • 4-SSM框架下定时任务调度实现

    千次阅读 2017-12-28 14:05:44
    今天想跟大家分享的是在ssm框架下实现任务调度实现方式。因为在我们实际的项目中,很多时候会使用到一些定时器,比如邮件汇总、报表汇总这样的业务场景。那在ssm框架下有什么方式实现呢?
  • 操作系统中任务调度实现

    万次阅读 2006-05-06 11:08:00
    31简介说起任务调度,我想我们大家都非常熟悉,任何一本操作系统教程,都对此描述得非常详细,不过,他们大多数都是站在理论高度,然而具体到某台实际的机器上,某个实际的操作系统当中,它们具体是怎样用代码实现...
  • public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host....
  • 通过Quartz实现定时任务调度

    千次阅读 2018-02-24 08:09:02
    上一篇文章很简单的叙述了基于Timer实现任务调度,因为基于Timer实现任务调度,只能设置延迟,一定的频率,所以并没有什么很大的优点,所以做过多介绍,现在使用最多的是 quartz实现的定时任务调度,quartz是一...
  • 上篇博文《任务调度(三)——Timer的替代品ScheduledExecutorService简介》已经对ScheduledExecutorService做了简单介绍,其实使用ScheduledExecutorService来替代Timer也是迫不得已的事情。主要原因如下: Timer不...
  • C语言实现任务调度与定时器

    千次阅读 2020-02-22 00:31:09
    代码实现是在xl2tpd的源码中get到的,感觉很有意思的一段代码。基本功能就是实现定时器,时间到后从定时队列中取出,然后完成指定的任务。 1. schedule.c代码(自己添加了main函数,用来调试) /* * Layer Two ...
  • 分布式任务调度实现方式

    千次阅读 2015-03-06 16:26:16
    背景  分布式任务调度是非常常见的一种应用场景,一般对可用性和...实际上任务调度实现有两种情况,第一种是通过mq来实现,mq做好了数据切分,负载均衡的效果,本文说的是另一种情况。 要
  • 从影响用户任务的执行效率和系统资源的使用效率的角度出发,在现有的云计算任务调度算法的基础上,进行理论创新,从模型高效和算法高效2个层面上设计云计算任务调度模型、算法并实现。 实验思路 实验主要分为两大...
  • DAG 任务调度实现以及优化

    千次阅读 2019-09-14 11:39:41
    DAG Task 任务调度算法 实现 github:https://github.com/smartxing/algorithm 1 有向图的构建 DAG dag = new DAG(); dag.addVertex("A"); dag.addVertex("B")...
  • 使用Spring的任务调度给我们的开发带来了极大的便利,不过当我们的任务调度配置完成后,很难再对其进行更改,除非停止服务器,修改配置,然后再重启,显然这样是不利于线上操作的,为了实现动态的任务调度修改,我在...
  • Quartz定时任务调度详细实现案例

    千次阅读 2015-11-18 15:49:42
    什么也不说直接上代码代码的注释中有相关的讲解。 1、代码结构及用到的一些Jar包: 2、获取Scheduler对象: package com.pitelin.quartz; import org.quartz.Scheduler; import org.quartz.SchedulerException;...
  • 嵌入式系统分时任务调度算法实现

    千次阅读 2014-06-02 13:22:55
    嵌入式高级特性编程之管理你的任务   2014酷玩创意     直奔主题,今天我们讲的是定时任务处理。简单讲就是我有一系列的任务需要在将来的某个特定时间由系统去处理。补充一下这里的将来去处理不是系统就可以...
  • 本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。 前言 任务调度是指基于给定...
  • 任务调度--spring下的任务调度quartz

    千次阅读 2015-09-13 09:14:38
    之前写过Timer实现任务调度,这篇文章用来写一下在spring下使用quartz实现任务调度,直接上代码: 定义任务对象:package com; /** * 1. 定义任务对象 * * @author Administrator * */ public class Data...
  • 几种任务调度的 Java 实现方法与比较  张 静, 软件工程师, IBM  王 启荣, 软件工程师, IBM  简介: 综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介 绍了几种任务调度的 Java 实现方法,...
  • 首先,说一下什么叫做任务调度 询问度娘中....看了一下,度娘说的还挺啰嗦的,实在不懂的可以去问问度娘,在这我就不粘贴了。就我的话来说,任务调度,就是在一个合适的时机去执行一项任务,当然这个时机是有计划的...
  • 任务调度类似于sqlserver中的作业,即按周期性执行某个程序,代码段,或者某种服务,在JAVA环境中出现了Quartz,它可以简单的实现任务的调试,而像lucene一样,它会有对于的.net版本,Quartz.net,今天我们来做一个...
  • js实现异步任务调度

    千次阅读 2020-03-13 23:40:58
    需求: 实现一个异步调度器函数,能够不断的将异步执行的函数加入... * 编写一个异步任务调度器 */ class Scheduler { list = [];//用来承载还未执行的异步 count = 0; //用来计数 async add(fn) { this....
  • java实现任务调度

    千次阅读 2018-09-23 00:33:40
    今天为什么要写点东西呢,因为我觉得自己现在开始有意思的去培养出写代码之外的能力,所以给自己定下一个目标,每个星期至少写点东西来。 聊些什么呢?那就聊自己的状态吧!因为觉得最近的状态自己特别二,干什么...
  • 1、说说轮巡任务调度与抢占式任务调度的区别? 答:轮询任务调度与抢占式任务调度的区别在于抢占式调度可以因为优先级高的任务抢占cpu,而轮询的不能。  2当软件线程个数超过硬件线程个数的时候,支持抢占式多任务...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 222,235
精华内容 88,894
关键字:

任务调度的代码实现