Java中实现定时任务的几种方式:
- Timer: java.util.Timer, 一个JDK自带的处理简单的定时任务的工具。
- ScheduledExecutorService: java.util.concurrent.ScheduledExecutorService, JDK中的定时任务接口,可以将定时任务与线程池结合使用。
- Sceduled: org.springframework.scheduling.annotation.Scheduled, Spring框架中基于注解来实现定时任务处理方式。
- Quartz: 支持分布式调度任务的开源框架。
Spring Task 与 Quartz 对比:
- Quartz提供了完整的分布式并发支持,Spring task的话还是需要自己手动配置线程池以及借助其他分布式工具实现。
- Quartz支持调度信息持久化,可以去Quartz官方网站下载建表sql,然后配置数据源等信息实现持久化, Spring task不支持持久化。
- Spring Task 使用起来非常简单Quartz还需要写其他配置相对来说比Spring Task复杂点。
需求简单的话可以直接使用 Spring Task ,比如一个简单的数据对接接口。如果会有复杂的业务需求不如一步到位,直接使Quartz。
一、Timer
Timer的schedule
和scheduleAtFixedRate
在任务计划时间和实际开始时间没有异常的情况下,当任务的执行时间超过设置的执行间隔,会丢失任务。schedule
不受异常时间的控制。
public class App {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new FooTimeTask("foo");
Calendar c = Calendar.getInstance();
c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
Date planTime = c.getTime();
System.out.println("计划执行时间" + planTime);
System.out.println("实际执行时间" + new Date());
t.schedule(task, planTime, 3000);
}
}
class FooTimeTask extends TimerTask {
private String name;
public FooTimeTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("name" + name + ",stime=" + new Date());
Thread.sleep(2000);
// Thread.sleep(4000);
System.out.println("name" + name + ",etime=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


public class App {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new FooTimeTask("foo");
t.scheduleAtFixedRate(task, new Date(), 3000);
}
}
class FooTimeTask extends TimerTask {
private String name;
public FooTimeTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("name" + name + ",stime=" + new Date());
Thread.sleep(2000);
//Thread.sleep(4000);
System.out.println("name" + name + ",etime=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


Timer的
scheduleAtFixedRate
在任务开始时间不正常情况下与schedule
有区别:存在时间追赶。
public class App {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new FooTimeTask("foo");
Calendar c = Calendar.getInstance();
c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
Date planTime = c.getTime();
System.out.println("计划执行时间" + planTime);
System.out.println("实际执行时间" + new Date());
t.scheduleAtFixedRate(task, planTime, 3000);
}
}
class FooTimeTask extends TimerTask {
private String name;
public FooTimeTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("name" + name + ",stime=" + new Date());
Thread.sleep(2000);
//Thread.sleep(4000);
System.out.println("name" + name + ",etime=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


二、ScheduledExecutorService
摘抄:java定时任务之Timer和ScheduledExecutorService
Timer的缺陷:
1、执行多个任务的时候,必须第一个执行完后才会执行第二个。
2、timer是单线程执行,因此一个任务抛异常,其它任务也不能执行了。
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
private static long start;
public static void main(String[] args) throws Exception {
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("task1 invoked ! "+(System.currentTimeMillis() - start));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("task2 invoked ! "+ (System.currentTimeMillis() - start));
}
};
Timer timer = new Timer();
start = System.currentTimeMillis();
timer.schedule(task1, 1000);
timer.schedule(task2, 3000);
}
}

newScheduledThreadPool:
1、可以多个线程同时执行不同的任务。
2、因为是多个线程所有,任务抛出异常,不影响其它任务的执行。
scheduleAtFixedRate
: 0s后开始执行任务,前一次任务的开始时间和下次任务的开始时间中间间隔是5s , 如果前一次任务执行时间大于5s ,那么下次任务会在队列中等待,直到前一次任务执行完后,再执行。
scheduleWithFixedDelay
: 0s后开始执行任务,前一次任务的结束时间和后一次任务的开始时间中间间隔是5s。
public class Scheduled {
static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
static class Task implements Runnable {
public void run() {
try {
Thread.sleep(2000L);
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
}
}
}
/**
* command:执行线程
* initialDelay:初始化延时
* period:两次开始执行最小间隔时间
* delay:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
* unit:计时单位
*/
public static void main(String[] args) {
scheduledExecutorService.scheduleAtFixedRate(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleWithFixedDelay(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
}
}
三、QuartZ
可参考:Java定时框架Quartz
- Quartz是功能强大的开源作业调度库,可以集成到最小的独立应用程序到最大的电子商务程序中。
- Quartz可以通过创建简单或者复杂的计划来执行成千上万的任务。
- 任何任务标准的Java组件,都可以执行相应的编程操作。
- Quartz Scheduler支持JTA事务和集群。
- Quartz实现了任务和触发器多对多的关系,可以将多个任务和不同的触发器相关联。

Quartz的核心:
-
JobBuilder
:创建Job和JobDetail。 -
JobDataMap
:可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap。 -
Job
:Job接口中只有一个方法execute(JobExecutionContext context)
;表示要执行的具体内容。
Job的属性有两种: 两者都是在值为true时表示任务被持久化或者保留;一个Job可以关联多个Trigger, 一个Trigger只能关联一个Job。
volatility : 表示任务是否被持久化到数据库存储;
durability : 没有trigger关联的时候任务是否保留; -
JobDetail
:表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。它本身可能是有状态的。 -
TriggerBuilder
:创建Trigger。 -
Trigger
:代表一个调度参数的配置。 -
Scheduler
:代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。
Quartz设计成JobDetail + Job的原因在于:
- JobDetail用于定义任务数据, 真正的执行任务的逻辑在Job中。
- 因为任务有可能是并发执行的,如果Scheduler直接使用Job, 会存在对同一个Job实例并发访问的问题;通过JobDetail绑定Job的方式 ,Scheduler每次执行,都会根据JobDetail创建一个新的Job实例,可以避免并发访问的问题。

Quartz的持久化:https://www.jianshu.com/p/d2f7ad108ab2

3.1 QuartZ入门
//@DisallowConcurrentExecution 禁止并发执行同一个Job定义的多个实例。
//@PersistJobDataAfterExecution 多个Job实例共享JobDataMap;对Trigger中的JobDataMap无效
public class MyJob implements Job {
//可以在JobDetail和Trigger中给Job中的属性赋值
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) {
//JobDetail的使用
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap triggerDataMap = jobExecutionContext.getTrigger().getJobDataMap();
JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
System.out.println("MyJob exe " + new Date() +" || "+ jobDataMap.get("job") +" || " + triggerDataMap.get("trigger")+"||"+mergedJobDataMap.get("com") +"||"+name);
System.out.println("=============");
//1.scheduler每次执行都会根据jobDetail创建一个新的Job实例。避免并发问题。
// 这样存在的问题:多个Job无法共享JobDataMap。
System.out.println("jobDetail"+System.identityHashCode(jobExecutionContext.getJobDetail()));
System.out.println("job"+System.identityHashCode(jobExecutionContext.getJobInstance()));
jobDataMap.put("count",jobDataMap.getInt("count")+1);
System.out.println("计数:"+jobDataMap.getInt("count"));
//2.默认是并发执行调度内的Job;Scheduler不会理会Job中的任务执行时间,严格按照调度执行。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
void quartz() throws Exception {
int count = 0;
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1","group1") //定义属性
.usingJobData("job","myJobDetail")
.usingJobData("com","jobcom")
.usingJobData("name","ABO") // 会给job中的属性自动赋值
.usingJobData("count",count)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","trigger1") //定义属性
.usingJobData("trigger","myTrigger")
.usingJobData("com","triggercom")
.usingJobData("name","ABO-trigger") // 会给job中的属性自动赋值
.startNow()
.withSchedule( //定义作业执行规则
SimpleScheduleBuilder.simpleSchedule() //简单的调度
.withIntervalInSeconds(1) //间隔时间
.repeatForever() //一直执行
).build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail,trigger);
//默认是并发执行调度内的Job;不会理会Job中的任务执行时间,严格按照调度执行。
scheduler.start();
//测试使用
Thread.sleep(10000);
}


3.2 Trigger
通用的触发器包括两类:
- SimpleTrigger: 根据Quartz框架的类中自定义的方法设置定时执行规则。
- CronTrigger: 基于Cron表达式实现触发器。
misfire:任务错过执行的处理。
优先级:同时触发的Trigger之间优先级高的先执行。
calendar:用于排除时间段。
misfire
错过触发的判断条件:
- job到达触发时间时候没有执行。
- 被执行的延迟时间超过了Quartz配置的misfireThreshold阈值。
错过触发的产生原因:
- 当job达到触发时间时,所有线程都被其他job占用了。
- 当job需要触发的时间点,scheduler停止了。
- job使用了@PersistJobDataAfterExecution。job不能并发执行,当到达下一个job执行点的时候,上一个任务还没有完成。
- job指定了过去的开始执行时间(在阈值范围内,还可以被正常执行)。
错过触发的应对策略:
-
SimpleTrigger
:
nowXXX相关的策略,会立即执行第一个misfire的任务,同时会修改startTime和repeatCount,因此finalFireTime会重新计算,原计划执行时间会被打乱。
nextXXX相关的策略,不会立即执行misfire的任务,也不会修改startTime和repeatCount,finalFireTime也没有改变,发生了misfire也还是按照原计划进行执行。 -
CronTrigger
:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:Quartz不会判断发生了misfire,立即执行所有发生了misfire的任务,然后按照原计划进行执行。
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:立即执行第一个发生misfire的任务,忽略其他发生misfire的任务,按照原计划继续执行。
MISFIRE_INSTRUCTION_DO_NOTHING:所有发生misfire的任务都被忽略,只是按照计划继续执行。
默认使用MISFIRE_INSTRUCTION_SMART_POLICY
策略。
3.3 scheduler
SchedulerFactory:
- 创建Scheduler。
- DireSchedulerFactory:在代码中定制Scheduler参数。
- StdSchedulerFactory:读取类路径下quartz.properties配置实例化Scheduler。
JobStore:
- 存储运行时的信息,包括:Trigger、Scheduler、JobDetail、业务锁等。
- RAMJobStore:内存实现。
- JobStoreTX:JDBC,事务由Quartz管理。
- JobStoreCMT:JDBC,使用容器事务。
- ClusteredJobStore:集群实现。
- TerracottaJobStore:Terracotta中间件。
ThreadPool:
- SimpleThreadPool。
- 自定义线程池。
3.4 SpringBoot整合Quartz
1.job类
public class QuartzDemo implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Execute..."+new Date());
}
}
2.配置类,配置触发器与任务调度器
/**
* Quartz配置类
*/
@Configuration
public class QuartzConfig {
/**
* 1、创建Job对象
*/
@Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
//关联我们自己的Job类
factoryBean.setJobClass(QuartzDemo.class);
return factoryBean;
}
/**
* 2、创建Trigger对象
*/
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
SimpleTriggerFactoryBean factoryBean=new SimpleTriggerFactoryBean();
//关联JobDetail对象
factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
//该参数表示一个执行的毫秒数
factoryBean.setRepeatInterval(2000); //每隔2秒执行一次
//重复次数
factoryBean.setRepeatCount(5);
return factoryBean;
}
/**
* 3、创建Scheduler
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
//关联trigger
factoryBean.setTriggers(simpleTriggerFactoryBean.getObject());
return factoryBean;
}
}
3.启动类
@SpringBootApplication
@EnableScheduling
public class QuartzApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(QuartzApplication.class, args);
}
}
网友评论