定时器在应用广泛,比如定时统计数据生成报表、每隔设定的时间提醒用户等。Java.util包自带的定时器Timer提供简单的定点执行功能,而Quartz是一个第三方提供的定时器框架。
Timer的任务是通过创建TimerTask子类进行实现,定时器由类Timer提供常见功能如下:
所有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;
@Override
public 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;
@Override
public 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;
@Override
public 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;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "延时循环定时任务执行中count3=" + (count3++));
}
};
// 2.任务调度
timer.schedule(task, 4000, 1000);
}
}
运行结果:
另起线程执行,延迟4秒后执行,每间隔一秒执行一次。
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", "[email protected]") //定义属性
.build();
//3、定义触发器Trigger,设置开始的时间及周期
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") //定义名称和所属的租
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1) //每隔2秒执行一次
.withRepeatCount(10)) //总共执行11次(第一次执行不基数)
.startNow()
.build();
//4、调度器指定要执行的任务JobDetail及触发器Trigger
scheduler.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。需要新建一个类实现Job接口重写execute方法定义任务。
JobDetail:描述这个Job是做什么的。
JobDataMap: 给Job提供参数用的。通过JobDetail.getJobDataMap获取
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", "[email protected]") //定义属性
.build();
//用JobDataMap 修改email
jobDetail.getJobDataMap().put("email", "[email protected]");
...
}
}
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 block
e.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();
//定义一个JobDetail
JobDetail jobDetail = newJob(DatabaseBackupJob.class)
.withIdentity("backupjob", "databasegroup")
.usingJobData("database", "how2java")
.build();
//调度加入这个job
scheduler.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秒),才会执行。
任务里发生异常是很常见的。 异常处理办法通常是两种:
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();
//定义一个JobDetail
JobDetail jobDetail = newJob(ExceptionJob1.class)
.withIdentity("exceptionJob1", "someJobGroup")
.build();
//调度加入这个job
scheduler.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();
//定义一个JobDetail
JobDetail jobDetail = newJob(ExceptionJob2.class)
.withIdentity("exceptionJob1", "someJobGroup")
.build();
//调度加入这个job
scheduler.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需要实现 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 block
e.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();
//定义一个JobDetail
JobDetail jobDetail = newJob(StoppableJob.class)
.withIdentity("exceptionJob1", "someJobGroup")
.build();
//调度加入这个job
scheduler.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 就是触发器的意思,用来指定什么时间开始触发,触发多少次,每隔多久触发一次。
常见触发器 SimpleTrigger、CronTrigger。
常见方法:
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", "[email protected]") //定义属性
.build();
//3、定义触发器Trigger,设置开始的时间及周期
SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1")
.startAt(startTime)
.build();
//4、调度器指定要执行的任务JobDetail及触发器Trigger
Date 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();
}
}
QuartzDemo:
...
/3、定义触发器Trigger,设置开始的时间及周期
SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.withRepeatCount(3)
.withIntervalInSeconds(1))
.build();
Cron 是Linux下的一个定时器,功能很强大,但是表达式更为复杂。CronTrigger 就是用Cron表达式来安排触发时间和次数的。
Cron表达式见《Cron表达式》
...
//3、定义触发器Trigger,设置开始的时间及周期
CronTrigger trigger = (CronTrigger) newTrigger().withIdentity("trigger1", "group1")
.withSchedule(cronSchedule("0/2 * * * * ?"))
.build();
Quartz的监听器有Job监听器、Trigger监听器、Scheduler监听器,对不同层面进行监控。实际业务用的较多的是Job监听器,用于监听器是否执行了,其他的用的相对较少,本知识主要讲解Job的。
监听器功能需要创建实现了 JobListener 接口的监听器类。
JobListener接口方法如下:
邮件监听器MailJobListener:
package 定时器;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MailJobListener implements JobListener {
@Override
public String getName() {
return "listener of mail job";
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
System.out.println("准备执行:\t "+jobExecutionContext.getJobDetail().getKey());
}
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
System.out.println("取消执行:\t "+jobExecutionContext.getJobDetail().getKey());
}
@Override
public 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 = mysqlDatabase
org.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
和以前的一样,没什么变化。
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());
}
}
新增加了一个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 block
resumeJobFromDatabase();
}
}
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();
// 定义一个JobDetail
JobDetail job = JobBuilder.newJob(MailJob.class) // 指定干活的类MailJob
.withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组
.usingJobData("email", "[email protected]") // 定义属性
.build();
// 调度加入这个job
scheduler.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应用会借助数据库这个桥梁探知到它不行了,从而接手把该进行的Job调度工作进行下去。
以这种方式保证任务调度的高可用性,即在发生异常重启等情况下,调度信息依然连贯性地进行下去,就好像Quartz应用从来没有中断过似的。
quartz.properties 在原来的基础上,增加3行:
org.quartz.jobStore.isClustered = true
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.clusterCheckinInterval = 1000
...
注:要进行集群,多个应用调度名称 instanceName 应该是一样的。
TestQuartz 不需要做改动,本例增加一些输出信息。
启动步骤:
上述相当于两个应用做了集群。
运行结果:
应用a:
应用b:
应用a先执行,运行20秒,就自动结束了。
应用b在应用a执行期间,不会执行。
应用b在应用b执行结束之后,检测到任务还未完成,自动把后续任务执行完毕。