任务调度的实现总结

任务调度的实现总结

前言

 我们的应用程序有些定时任务(例如想在凌晨十二点半统计某个互联网金融公司一款借款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());
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值