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