概述

定时器在应用广泛,比如定时统计数据生成报表、每隔设定的时间提醒用户等。Java.util包自带的定时器Timer提供简单的定点执行功能,而Quartz是一个第三方提供的定时器框架。

对比

  • Timer

    • 优点:

      • java.util包自带的,Timer的任务是通过创建TimerTask子类进行实现,使用方便。
    • 缺点:
      • 定时器没有持久性机制。
      • 定时器不能灵活的调度(只能设置开始时间和重复间隔,没有基于日期,一天中的时间等)
      • 定时器不使用线程池(每个定时器一个线程)
      • 定时器没有真正的管理方案,必须编写自己的机制管理。
  • Quartz
    • 优点:

      • Quartz是一个作业调度库,可以与任何其他软件系统集成,也可以和其他软件系统一起使用。
      • Quartz非常灵活,可以灵活、准确的控制日期节点以及执行次数。
      • Quartz非常轻量级,只需要很少的配置即可完成需求,“开箱即用”。
    • 缺点:
      • Quartz必须要新建一个class文件实现Job接口重写execute方法定义任务。

使用方法

Timer

Timer的任务是通过创建TimerTask子类进行实现,定时器由类Timer提供常见功能如下:

  • schedule(TimerTask task, Date time):在time时间点执行task任务一次。
  • schedule(TimerTask task, long delay):在延迟delay毫秒后执行task任务一次。
  • schedule(TimerTask task, Date firstTime, long period):在firsttime时间点执行task一次,之后定期period毫秒时间执行task。时间如果为过去时间, 不会执行过去没有执行的任务, 但是会马上执行。
  • schedule(TimerTask task, long delay, long period):在延迟delay后执行task一次,之后定期period毫秒时间执行task。时间如果为过去时间, 不会执行过去没有执行的任务, 但是会马上执行。

所有delay和period都是long类型的延迟时间,单位为毫秒

指定开始时间

package 定时器;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;public class TimerDemo {public static void main(String[] args) {method();System.out.println("main执行完成");}public static void method() {// 1、创建Timer对象用于定义定时器的任务及开始时间、周期Timer timer = new Timer();// 2、创建匿名内部类,定义任务TimerTask task = new TimerTask() {int count = 1;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "指定时间定时任务执行中count=" + (count++));}};// 3.任务调度timer.schedule(task, new Date());}
}

运行结果:

结果看出,main执行完成后推出,而定时任务另起线程执行等待。

指定开始时间及执行周期

开始时间为当前时间,每一秒执行一次。

public class TimerDemo {public static void main(String[] args) {method();method2();System.out.println("main执行完成");}public static void method() {// 1、创建Timer对象用于定义定时器的任务及开始时间、周期Timer timer = new Timer();// 2、创建匿名内部类,定义任务TimerTask task = new TimerTask() {int count = 1;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "指定时间定时任务执行中count=" + (count++));}};// 3.任务调度timer.schedule(task, new Date());}public static void method2() {// 1.创建Timer对象用于定义定时器的任务及开始时间、周期Timer timer = new Timer();// 创建匿名内部类,定义任务TimerTask task = new TimerTask() {int count2 = 1;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "指定时间循环定时任务执行中count2=" + (count2++));}};// 2.任务调度 毫秒值timer.schedule(task, new Date(), 1000);}
}

运行结果:

另起线程执行,每间隔一秒执行一次。

延期执行及执行周期

延迟4秒后执行,每秒执行一次。

public class TimerDemo {public static void main(String[] args) {method();method2();method3();System.out.println("main执行完成");}public static void method() {...}public static void method2() {...}public static void method3() {// 1.创建Timer对象用于定义定时器的任务及开始时间、周期Timer timer = new Timer();// 创建匿名内部类,定义任务TimerTask task = new TimerTask() {int count3 = 1;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "延时循环定时任务执行中count3=" + (count3++));}};// 2.任务调度timer.schedule(task, 4000, 1000);}
}

运行结果:

另起线程执行,延迟4秒后执行,每间隔一秒执行一次。

Quartz

Quartz执行需要1、创建一个SchedulerFactory对象用于生产调度器-Scheduler对象;2、创建调度所需要的任务 任务-Job;3、指定开始的时间和执行周期 触发器-Trigger

需要jar包:quartz-*.jar、slf4j-api-*.jar

示例

package QuartzTest;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.newJob;public class TestQuartz {public static void main(String[] args) throws Exception{//1、创建工厂对象,用于生产调度器Scheduler对象Scheduler scheduler = new StdSchedulerFactory().getScheduler();//2、创建任务(JobDetail),具体的任务需要自定义类实现Job接口JobDetail jobDetail = newJob(MailJob.class) //指定干活的类MailJob.withIdentity("mailjob1", "mailgroup") //定义任务名称和分组.usingJobData("email", "admin@10086.com") //定义属性.build();//3、定义触发器Trigger,设置开始的时间及周期Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") //定义名称和所属的租.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1) //每隔2秒执行一次.withRepeatCount(10)) //总共执行11次(第一次执行不基数).startNow().build();//4、调度器指定要执行的任务JobDetail及触发器Triggerscheduler.scheduleJob(jobDetail, trigger);//5、启动scheduler.start();//6、等待15秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(15000);scheduler.shutdown(true);System.out.printf(Thread.currentThread().getName() + " main关闭");}
}

自定义任务类MailJob实现Job接口:

package 定时器;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;public class MailJob implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {JobDetail detail = context.getJobDetail();String email = detail.getJobDataMap().getString("email");SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");String now = sdf.format(new Date());System.out.printf("["+Thread.currentThread().getName()+"]" + new Date() +" 给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n" ,email, now);}
}

运行结果:

.withIdentity(“mailjob1”, “mailgroup”)用于分组。
withIdentity定义任务名称mailjob1和组名mailgroup。比如一个系统有3个job 是备份数据库的,有4个job 是发邮件的,那么对他们进行分组,可以方便管理,类似于一次性停止所有发邮件的这样的操作。

任务-JobDetail

调度所需要的任务-JobDetail。需要新建一个类实现Job接口重写execute方法定义任务。
JobDetail:描述这个Job是做什么的。
JobDataMap: 给Job提供参数用的。通过JobDetail.getJobDataMap获取

  • getString(String key):获取参数值
  • put(String key, String value):设置参数值
public class TestQuartz {public static void main(String[] args) throws Exception{...//2、创建任务(JobDetail),具体的任务需要自定义类实现Job接口JobDetail jobDetail = newJob(MailJob.class) //指定干活的类MailJob.withIdentity("mailjob1", "mailgroup") //定义任务名称和分组.usingJobData("email", "admin@10086.com") //定义属性.build();//用JobDataMap 修改emailjobDetail.getJobDataMap().put("email", "admin@taobao.com");...}
}

输出:

Job 并发

Quartz定时任务默认都是并发执行的,无论上一次任务是否结束或者完成,只要间隔时间到就会执行下一次, 因为如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。

数据执行任务DatabaseBackupJob:

package QuartzTest;
import org.quartz.*;
import java.util.Date;public class DatabaseBackupJob implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {JobDetail detail = context.getJobDetail();String database = detail.getJobDataMap().getString("database");System.out.printf("["+Thread.currentThread().getName()+"]" + new Date() +" 给数据库 %s 备份, 耗时10秒 %n" ,database);try {Thread.sleep(10000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}

TestQuartz:

public class TestQuartz {public static void main(String[] args) throws Exception{databaseCurrentJob();}private static void databaseCurrentJob() throws Exception {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(2).withRepeatCount(10)).build();//定义一个JobDetailJobDetail jobDetail = newJob(DatabaseBackupJob.class).withIdentity("backupjob", "databasegroup").usingJobData("database", "how2java").build();//调度加入这个jobscheduler.scheduleJob(jobDetail, trigger);//启动scheduler.start();//等待100秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(100000);scheduler.shutdown(true);}
}

运行结果:

由结果看出,任务并没有等一个任务执行完成,再执行下一个任务。而是等待2秒就执行下一个任务。
但是有时候会做长时间的任务,比如上述数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成数据库被锁死 (几个线程同时备份数据库,引发无法预计的混乱)。

那怎么实现呢?给任务增加注解 @DisallowConcurrentExecution
数据执行任务DatabaseBackupJob:

package QuartzTest;
import org.quartz.*;
import java.util.Date;@DisallowConcurrentExecution
public class DatabaseBackupJob implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {...}
}

执行结果:

由结果看出,任务会等前一个任务执行完成(执行10秒),才会执行。

Job 异常

任务里发生异常是很常见的。 异常处理办法通常是两种:

  • setUnscheduleAllTriggers:当异常发生,那么就通知所有管理这个 Job 的调度,停止运行它。
  • setRefireImmediately:当异常发生,修改一下参数,马上重新运行。
    ExceptionJob1:
public class ExceptionJob1  implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {int i = 0;try {//故意发生异常System.out.println(100/i);} catch (Exception e) {System.out.println("["+Thread.currentThread().getName()+"]" + new Date() + " 发生了异常,取消这个Job 对应的所有调度");JobExecutionException je =new JobExecutionException(e);je.setUnscheduleAllTriggers(true);throw je;}}
}

ExceptionJob2:

public class ExceptionJob2  implements Job {static int i = 0;public void execute(JobExecutionContext context) throws JobExecutionException {try {//故意发生异常System.out.println("["+Thread.currentThread().getName()+"]" + new Date() + " 运算结果"+100/i);} catch (Exception e) {System.out.println("["+Thread.currentThread().getName()+"]" + new Date() + " 发生了异常,修改一下参数,立即重新执行");i = 1;JobExecutionException je =new JobExecutionException(e);je.setRefireImmediately(true);throw je;}}
}

TestQuartz:

public class TestQuartz {public static void main(String[] args) throws Exception{exceptionHandle1();//exceptionHandle2();}private static void exceptionHandle1() throws Exception {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(2).withRepeatCount(5)).build();//定义一个JobDetailJobDetail jobDetail = newJob(ExceptionJob1.class).withIdentity("exceptionJob1", "someJobGroup").build();//调度加入这个jobscheduler.scheduleJob(jobDetail, trigger);//启动scheduler.start();//等待20秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(10000);scheduler.shutdown(true);}private static void exceptionHandle2() throws Exception {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(2).withRepeatCount(5)).build();//定义一个JobDetailJobDetail jobDetail = newJob(ExceptionJob2.class).withIdentity("exceptionJob1", "someJobGroup").build();//调度加入这个jobscheduler.scheduleJob(jobDetail, trigger);//启动scheduler.start();//等待20秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(10000);scheduler.shutdown(true);}
}

运行结果:

执行exceptionHandle2():

public class TestQuartz {public static void main(String[] args) throws Exception{//exceptionHandle1();exceptionHandle2();}
}

运行结果:

中断 Job

在业务上,有时候需要中断任务,那么这个Job需要实现 InterruptableJob 接口,才可以被中断。
StoppableJob:

package QuartzTest;
import org.quartz.InterruptableJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException;import java.util.Date;//必须实现InterruptableJob 而非 Job才能够被中断
public class StoppableJob implements InterruptableJob {private boolean stop = false;public void execute(JobExecutionContext context) throws JobExecutionException {while(true){if(stop)break;try {System.out.println("["+Thread.currentThread().getName()+"]" + new Date() +" 每隔1秒,进行一次检测,看看是否停止");Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("["+Thread.currentThread().getName()+"]" + new Date() +" 持续工作中。。。");}}public void interrupt() throws UnableToInterruptJobException {System.out.println("["+Thread.currentThread().getName()+"]" + new Date() +" 被调度叫停");stop = true;}
}

TestQuartz :

public class TestQuartz {public static void main(String[] args) throws Exception{stop();}private static void stop() throws Exception {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startNow().build();//定义一个JobDetailJobDetail jobDetail = newJob(StoppableJob.class).withIdentity("exceptionJob1", "someJobGroup").build();//调度加入这个jobscheduler.scheduleJob(jobDetail, trigger);//启动scheduler.start();Thread.sleep(5000);System.out.println("过5秒,调度停止 job");//key 就相当于这个Job的主键scheduler.interrupt(jobDetail.getKey());//等待20秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(10000);scheduler.shutdown(true);}}

运行结果:

触发器-Trigger

指定开始的时间和执行周期。Trigger 就是触发器的意思,用来指定什么时间开始触发,触发多少次,每隔多久触发一次。
常见触发器 SimpleTrigger、CronTrigger。

SimpleTrigger

常见方法:

  • withIdentity(String name, String group):设置触发器名称、组名
  • startNow():立即执行
  • startAt(Date triggerStartTime):指定时间点执行

10秒后运行

DateBuilder.futureDate():可以方便的获取10秒后, 5分钟后, 3个小时候,2个月后这样的时间。
QuartzDemo:

public class QuartzDemo {public static void main(String[] args) throws SchedulerException {//1、创建工厂对象,用于生产调度器Scheduler对象Scheduler scheduler = new StdSchedulerFactory().getScheduler();Date startTime = DateBuilder.futureDate(10, DateBuilder.IntervalUnit.SECOND);//2、创建任务(JobDetail),具体的任务需要自定义类实现Job接口JobDetail jobDetail = JobBuilder.newJob(MailJob.class) //指定干活的类MailJob.withIdentity("mailjob1", "mailgroup") //定义任务名称和分组.usingJobData("email", "admin@10086.com") //定义属性.build();//3、定义触发器Trigger,设置开始的时间及周期SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();//4、调度器指定要执行的任务JobDetail及触发器TriggerDate ft = scheduler.scheduleJob(jobDetail, trigger);System.out.println("当前时间是:" + new Date().toLocaleString());System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());//5、启动scheduler.start();}
}

运行结果:

累计n次,间隔n秒

  • withSchedule

    • withIntervalInSeconds(n) :每隔n秒执行一次
    • withRepeatCount(n)) :总共执行n+1次(第一次执行不基数)
    • repeatForever():无限重复

QuartzDemo:

...
/3、定义触发器Trigger,设置开始的时间及周期SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).withSchedule(simpleSchedule().withRepeatCount(3).withIntervalInSeconds(1)).build();

运行结果:

CronTrigger

Cron 是Linux下的一个定时器,功能很强大,但是表达式更为复杂。CronTrigger 就是用Cron表达式来安排触发时间和次数的。

Cron表达式见《Cron表达式》

每隔2秒执行一次

...
//3、定义触发器Trigger,设置开始的时间及周期CronTrigger trigger = (CronTrigger) newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/2 * * * * ?")).build();

运行结果:

调度器-Scheduler

  • Date scheduleJob(JobDetail var1, Trigger var2):将任务和触发器加入调度器
  • start():启动
  • shutdown():关闭

监听器

Quartz的监听器有Job监听器、Trigger监听器、Scheduler监听器,对不同层面进行监控。实际业务用的较多的是Job监听器,用于监听器是否执行了,其他的用的相对较少,本知识主要讲解Job的。
监听器功能需要创建实现了 JobListener 接口的监听器类。
JobListener接口方法如下:

  • public String getName():返回JobListener名称。对于注册为全局的监听器,getName()主要用于记录日志,对于由特定Job引用的 JobListener,注册在 JobDetail 上的监听器名称必须匹配从监听器上getName()返回值。
  • public void jobToBeExecuted(JobExecutionContext jobExecutionContext):Scheduler在 JobDetail 将要被执行时调用的方法。
  • public void jobExecutionVetoed(JobExecutionContext jobExecutionContext):Scheduler在 JobDetail即将被执行,但又被 Triggeristener否决了调用的方法。
  • public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e):Scheduler在 JobDetail 被执行之后调用的方法。

邮件监听器MailJobListener:

package 定时器;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;public class MailJobListener implements JobListener {@Overridepublic String getName() {return "listener of mail job";}@Overridepublic void jobToBeExecuted(JobExecutionContext jobExecutionContext) {System.out.println("准备执行:\t "+jobExecutionContext.getJobDetail().getKey());}@Overridepublic void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {System.out.println("取消执行:\t "+jobExecutionContext.getJobDetail().getKey());}@Overridepublic void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {System.out.println("执行结束:\t "+jobExecutionContext.getJobDetail().getKey());System.out.println();}
}

运行结果:

数据库存储

Quartz的触发器、调度、任务等信息都是放在内存中的。不能对执行进度进行实时查看,而且一旦系统异常,信息就会丢失。
所以Quartz还提供了另一个方式,可以把这些信息存放在数据库中,叫做 JobStoreTX。运行状态信息存放在数据库中。

建表

DROP DATABASE IF EXISTS quartz;
CREATE DATABASE quartz DEFAULT CHARACTER SET utf8;
USE quartz;DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;CREATE TABLE QRTZ_JOB_DETAILS(SCHED_NAME VARCHAR(120) NOT NULL,JOB_NAME  VARCHAR(100) NOT NULL,JOB_GROUP VARCHAR(100) NOT NULL,DESCRIPTION VARCHAR(250) NULL,JOB_CLASS_NAME   VARCHAR(250) NOT NULL,IS_DURABLE VARCHAR(1) NOT NULL,IS_NONCONCURRENT VARCHAR(1) NOT NULL,IS_UPDATE_DATA VARCHAR(1) NOT NULL,REQUESTS_RECOVERY VARCHAR(1) NOT NULL,JOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);CREATE TABLE QRTZ_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(100) NOT NULL,TRIGGER_GROUP VARCHAR(100) NOT NULL,JOB_NAME  VARCHAR(100) NOT NULL,JOB_GROUP VARCHAR(100) NOT NULL,DESCRIPTION VARCHAR(250) NULL,NEXT_FIRE_TIME BIGINT(13) NULL,PREV_FIRE_TIME BIGINT(13) NULL,PRIORITY INTEGER NULL,TRIGGER_STATE VARCHAR(16) NOT NULL,TRIGGER_TYPE VARCHAR(8) NOT NULL,START_TIME BIGINT(13) NOT NULL,END_TIME BIGINT(13) NULL,CALENDAR_NAME VARCHAR(100) NULL,MISFIRE_INSTR SMALLINT(2) NULL,JOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);CREATE TABLE QRTZ_SIMPLE_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(100) NOT NULL,TRIGGER_GROUP VARCHAR(100) NOT NULL,REPEAT_COUNT BIGINT(7) NOT NULL,REPEAT_INTERVAL BIGINT(12) NOT NULL,TIMES_TRIGGERED BIGINT(10) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);CREATE TABLE QRTZ_CRON_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(100) NOT NULL,TRIGGER_GROUP VARCHAR(100) NOT NULL,CRON_EXPRESSION VARCHAR(100) NOT NULL,TIME_ZONE_ID VARCHAR(80),PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);CREATE TABLE QRTZ_SIMPROP_TRIGGERS(         SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(100) NOT NULL,TRIGGER_GROUP VARCHAR(100) NOT NULL,STR_PROP_1 VARCHAR(512) NULL,STR_PROP_2 VARCHAR(512) NULL,STR_PROP_3 VARCHAR(512) NULL,INT_PROP_1 INT NULL,INT_PROP_2 INT NULL,LONG_PROP_1 BIGINT NULL,LONG_PROP_2 BIGINT NULL,DEC_PROP_1 NUMERIC(13,4) NULL,DEC_PROP_2 NUMERIC(13,4) NULL,BOOL_PROP_1 VARCHAR(1) NULL,BOOL_PROP_2 VARCHAR(1) NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);CREATE TABLE QRTZ_BLOB_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(100) NOT NULL,TRIGGER_GROUP VARCHAR(100) NOT NULL,BLOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);CREATE TABLE QRTZ_CALENDARS(SCHED_NAME VARCHAR(120) NOT NULL,CALENDAR_NAME  VARCHAR(100) NOT NULL,CALENDAR BLOB NOT NULL,PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_GROUP  VARCHAR(100) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);CREATE TABLE QRTZ_FIRED_TRIGGERS(SCHED_NAME VARCHAR(120) NOT NULL,ENTRY_ID VARCHAR(95) NOT NULL,TRIGGER_NAME VARCHAR(100) NOT NULL,TRIGGER_GROUP VARCHAR(100) NOT NULL,INSTANCE_NAME VARCHAR(100) NOT NULL,FIRED_TIME BIGINT(13) NOT NULL,SCHED_TIME BIGINT(13) NOT NULL,PRIORITY INTEGER NOT NULL,STATE VARCHAR(16) NOT NULL,JOB_NAME VARCHAR(100) NULL,JOB_GROUP VARCHAR(100) NULL,IS_NONCONCURRENT VARCHAR(1) NULL,REQUESTS_RECOVERY VARCHAR(1) NULL,PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);CREATE TABLE QRTZ_SCHEDULER_STATE(SCHED_NAME VARCHAR(120) NOT NULL,INSTANCE_NAME VARCHAR(100) NOT NULL,LAST_CHECKIN_TIME BIGINT(13) NOT NULL,CHECKIN_INTERVAL BIGINT(13) NOT NULL,PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);CREATE TABLE QRTZ_LOCKS(SCHED_NAME VARCHAR(120) NOT NULL,LOCK_NAME  VARCHAR(40) NOT NULL,PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);commit;

配置文件

Quartz默认加载工程目录下的quartz.properties,如果工程目录下没有,就会去加载quartz.jar包下面的quartz.properties文件。
故,在src下新建 quartz.properties 配置文件,里面指定使用 JobStoreTX 方式管理任务。

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = mysqlDatabaseorg.quartz.dataSource.mysqlDatabase.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.mysqlDatabase.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
org.quartz.dataSource.mysqlDatabase.user = root
org.quartz.dataSource.mysqlDatabase.password = admin
org.quartz.dataSource.mysqlDatabase.maxConnections = 5

MailJob

和以前的一样,没什么变化。

package 定时器;
import org.quartz.*;
import java.text.SimpleDateFormat;
import java.util.Date;@DisallowConcurrentExecution
public class MailJob implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {JobDetail detail = context.getJobDetail();String email = detail.getJobDataMap().getString("email");SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");String now = sdf.format(new Date());System.out.printf("["+Thread.currentThread().getName()+"]" + new Date() +" 给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s(%s) %n" ,email, now,context.isRecovering());}
}

TestQuartz

新增加了一个resumeJobFromDatabase 方法,当使用原来的方式增加任务报异常的时候,就直接从数据库重跑任务。

package 定时器;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.TriggerBuilder.newTrigger;public class QuartzDemo {public static void main(String[] args) throws Exception {try {assginNewJob();} catch (ObjectAlreadyExistsException e) {System.err.println("发现任务已经在数据库存在了,直接从数据库里运行:"+ e.getMessage());// TODO Auto-generated catch blockresumeJobFromDatabase();}}private static void resumeJobFromDatabase() throws Exception {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();scheduler.start();// 等待200秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(200000);scheduler.shutdown(true);}private static void assginNewJob() throws SchedulerException, InterruptedException {// 创建调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 定义一个触发器Trigger trigger = newTrigger().withIdentity("trigger1", "group1") // 定义名称和所属的租.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(15) // 每隔15秒执行一次.withRepeatCount(10)) // 总共执行11次(第一次执行不基数).build();// 定义一个JobDetailJobDetail job = JobBuilder.newJob(MailJob.class) // 指定干活的类MailJob.withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组.usingJobData("email", "admin@10086.com") // 定义属性.build();// 调度加入这个jobscheduler.scheduleJob(job, trigger);// 启动scheduler.start();// 等待20秒,让前面的任务都执行完了之后,再关闭调度器Thread.sleep(20000);scheduler.shutdown(true);}
}

第一次运行:

QRTZ_SIMPLE_TRIGGERS:

QRTZ_TRIGGERS:

QRTZ_JOB_DETAILS:

第二次运行:

注意:如果任务执行完成,上述数据库表数据均被清空。如果在执行期间,查看数据库表,表QRTZ_TRIGGERS、QRTZ_JOB_DETAILS数据不变,表QRTZ_SIMPLE_TRIGGERS会实时记录执行次数:

字段REPEAT_COUNT记录还需要执行的总次数,字段TIMES_TRIGGERED记录执行过的次数。

Quartz集群

所谓的Quartz集群,是指在基于数据库存储Quartz调度信息的基础上,有多个一模一样的 Quartz 应用在运行。
当某一个Quartz应用重启或者发生问题的时候,其他的Quartz应用会借助数据库这个桥梁探知到它不行了,从而接手把该进行的Job调度工作进行下去。
以这种方式保证任务调度的高可用性,即在发生异常重启等情况下,调度信息依然连贯性地进行下去,就好像Quartz应用从来没有中断过似的。

quartz.properties

quartz.properties 在原来的基础上,增加3行:

org.quartz.jobStore.isClustered = true
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.clusterCheckinInterval = 1000...
  • org.quartz.jobStore.isClustered = true:开启集群
  • org.quartz.scheduler.instanceId = AUTO:要进行集群,多个应用调度id instanceId 必须不一样,这里使用AUTO,就会自动分配不同的ID。 目测是本机机器名称加上时间戳
  • org.quartz.jobStore.clusterCheckinInterval = 1000:每个一秒钟去数据库检查一下,在其他应用挂掉之后及时补上

注:要进行集群,多个应用调度名称 instanceName 应该是一样的。

TestQuartz

TestQuartz 不需要做改动,本例增加一些输出信息。
启动步骤:

  1. 启动一次 TestQuartz,叫做 a 应用
  2. 紧接着(在几秒钟内)再次启动 TestQuartz,叫做 b 应用
  3. 使用多控制台显示方式,在两个不同的控制台观察现象

上述相当于两个应用做了集群。
运行结果:
应用a:

应用b:

应用a先执行,运行20秒,就自动结束了。
应用b在应用a执行期间,不会执行。
应用b在应用b执行结束之后,检测到任务还未完成,自动把后续任务执行完毕。

Java定时器Timer和第三方定时器Quartz相关推荐

  1. Java 中Timer和TimerTask 定时器和定时任务使用的例子

    转载自  Java 中Timer和TimerTask 定时器和定时任务使用的例子 这两个类使用起来非常方便,可以完成我们对定时器的绝大多数需求 Timer类是用来执行任务的类,它接受一个TimerTa ...

  2. java定时器timer配置_java定时器配置解析

    定时器是java的一大特色,本篇文章我们会了解定时器的配置有哪些方式,下面就跟小编一起看看吧. 实例package com.wxltsoft.tool; import org.junit.Test; ...

  3. 定时器Timer的实现

    定时器Timer的实现 定时器在实际项目中会用的比较平凡.因此,本文首先介绍定时器Timer的windows版本,跨平台的定时器将在下一篇文章中介绍.它们的源代码均用C++编写.源代码详见:https ...

  4. 封装Libuv定时器 - Timer

    封装Libuv定时器 - Timer   Libuv底层定时器采用小顶堆结构管理,即最快超时的定时器句柄会放在最上面. Timer.h #pragma once#include "uv.h& ...

  5. Java定时器Timer

    Java定时器Timer 在JDK库中,Timer类主要负责计划任务的功能,也就是在指定的时开始执行某一个任务.Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,执行计划 ...

  6. Java并发编程—定时器Timer底层原理

    原文作者:妮蔻 原文地址:Java并发编程笔记之Timer源码分析 目录 一.timer问题复现 二.Timer 实现原理分析 timer在JDK里面,是很早的一个API了.具有延时的,并具有周期性的 ...

  7. 【定时任务】JDK java.util.Timer定时器的实现原理

    在程序中简单实用Timer的方法,参考学习. 定时任务,也叫定时器,是指在指定的时间执行指定任务的机制,类似于Windows自带的计划任务.JDK中提供了定时器的支持-java.util.Timer, ...

  8. JAVA程序设计计时器代码_Java中的定时器Timer使用示例代码详解

    一.类概述 Timer是一种定时器工具,用来在一个后台线程计划执行指定任务.它可以计划执行一个任务一次或反复多次. TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务. 二.代码 ...

  9. java项目中多个定时器_在java项目中如何使用Timer定时器

    在java项目中如何使用Timer定时器 发布时间:2020-11-16 16:36:16 来源:亿速云 阅读:97 作者:Leah 在java项目中如何使用Timer定时器?很多新手对此不是很清楚, ...

最新文章

  1. ‘xxfile' Missing the class attribute 'ExtensionOfNativeClass'
  2. FastReport4.6程序员手册_翻译 转
  3. camel apache_Apache Camel 3的工作终于开始了
  4. markdown引入代码_给你自己的博客加个 Markdown
  5. 蓝桥杯 ALGO-126 算法训练 水仙花
  6. python列表的排序方法是_Python列表排序 reverse、sort、sorted 操作方法详解
  7. 服务器,路由器,交换机产品解读
  8. MATLAB实现SVM多分类(one-vs-rest),利用自带函数fitcsvm
  9. 精灵五笔 优化指南【原】
  10. 《大数据技术原理与应用》(第三章 HDFS 课后答案)
  11. 【机器人学导论知识点+习题笔记2.1~2.13】(间歇性更新)
  12. 从autotool迁移到cmake
  13. 浅析浏览器 Web 视频播放器
  14. 数值计算笔记之数值计算中应注意的问题
  15. 乙酸乙酯密度是多少 乙酸乙酯的用途
  16. 概念模型向逻辑模型的转换规则
  17. mbp touchbar设置_Macbook Pro 上 有什么好的 Touchbar 使用技巧?
  18. 线性代数中满足乘法交换律的运算-行列式与迹
  19. 使用函数提取姓别和出生日期:
  20. 【智能制造】生产异常情况的处理流程

热门文章

  1. DEMOS和LDMOS的区别
  2. 咖啡泡JAVA_【转】咖啡—冲泡方式
  3. UVALive 4513 Stammering Aliens
  4. 聊城大学计算机科学导论期末考试,09101计算机导论试卷a(含答案
  5. 如何确定多少个簇?聚类算法中选择正确簇数量的三种方法
  6. c语言的文字游戏,C语言之文字游戏
  7. 系统性能监控工具ssar实例精选 | 龙蜥SIG
  8. 关于加入@RequestBody后请求报错:Required request body is missing:
  9. 电信诈骗为何如此难以根治?
  10. 毕设-基于LoRa的智能农业大棚(三)