这几个概念类,是我们调用Quartz任务调度的基础。了解清楚之后,我们再来看一下如何去启动和关闭一个Quartz调度程序。
1、org.quartz.Job
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
import
org.quartz.JobBuilder;
import
org.quartz.JobDetail;
import
org.quartz.Scheduler;
import
org.quartz.SchedulerException;
import
org.quartz.SchedulerFactory;
import
org.quartz.SimpleScheduleBuilder;
import
org.quartz.Trigger;
import
org.quartz.TriggerBuilder;
import
org.quartz.impl.StdSchedulerFactory;
public
class
Quartz {
public
static
void
main(String[] args) {
// 1、创建JobDetial对象 , 并且设置选项
JobDetail jobDetail= JobBuilder.newJob(MyJob.
class
).withIdentity(
"testJob_1"
,
"group_1"
).build();
// 2、通过 TriggerBuilder 创建Trigger对象
TriggerBuilder
triggerBuilder.withIdentity(
"trigger_1"
,
"group_1"
);
triggerBuilder.startNow();
// 设置重复次数和间隔时间
triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(
1
)
//时间间隔
.withRepeatCount(
5
)
// 重复次数
);
// 设置停止时间
//triggerBuilder.endAt(new Date(System.currentTimeMillis() + 3));
// 创建Trigger对象
Trigger trigger = triggerBuilder.build();
// 3、创建Scheduler对象,并配置JobDetail和Trigger对象
SchedulerFactory sf =
new
StdSchedulerFactory();
try
{
Scheduler scheduler = sf.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
// 4、并执行启动、关闭等操作
scheduler.start();
//关闭调度器
//scheduler.shutdown(true);
}
catch
(SchedulerException e) {
e.printStackTrace();
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
import
java.util.Date;
import
org.quartz.Job;
import
org.quartz.JobExecutionContext;
import
org.quartz.JobExecutionException;
public
class
MyJob
implements
Job{
public
void
execute(JobExecutionContext jobExecutionContext)
throws
JobExecutionException {
System.out.println(
new
Date() +
": doing something..."
);
}
}
|
执行结果:
Wed Aug 03 19:12:53 CST 2016: doing something…
Wed Aug 03 19:12:53 CST 2016: doing something…
Wed Aug 03 19:12:53 CST 2016: doing something…
Wed Aug 03 19:12:53 CST 2016: doing something…
Wed Aug 03 19:12:53 CST 2016: doing something…
Wed Aug 03 19:12:53 CST 2016: doing something…
当把结束时间改为:
1
2
|
// 设置停止时间
triggerBuilder.endAt(
new
Date(System.currentTimeMillis() +
3
));
|
执行结果:
Wed Aug 03 19:17:12 CST 2016: doing something…
Wed Aug 03 19:17:12 CST 2016: doing something…
Wed Aug 03 19:17:12 CST 2016: doing something…
结果分析:
可以看出,设置重复执行5次,没设置停止时间的时候,执行了6次,当停止时间设置为new Date(System.currentTimeMillis() + 3时, 果只执行了3次。 也就是说当到了停止时间,不管执行的次数是否到达最大次数,都不在执行了。
当添加了关闭调度器:
1
2
|
//关闭调度器
scheduler.shutdown(
true
);
|
执行结果:
Wed Aug 03 19:17:12 CST 2016: doing something…
quartz运行时由QuartzSchedulerThread类作为主体,循环执行调度流程。JobStore作为中间层,按照quartz的并发策略执行数据库操作,完成主要的调度逻辑。JobRunShellFactory负责实例化JobDetail对象,将其放入线程池运行。LockHandler负责获取LOCKS表中的数据库锁。
整个quartz对任务调度的时序大致如下:
0.调度器线程run()
1.获取待触发trigger
1.1读取JobDetail信息
1.2读取trigger表中触发器信息并标记为”已获取”2.触发trigger
2.1确认trigger的状态
2.2读取trigger的JobDetail信息
2.3读取trigger的Calendar信息
2.4更新trigger信息3实例化并执行Job
3.1从线程池获取线程执行JobRunShell的run方法
我们看到初始化一个调度器需要用工厂类获取实例:
1
2
3
4
|
SchedulerFactory sf =
new
StdSchedulerFactory();
Scheduler sch = sf.getScheduler();
// 然后启动:
sch.start();
|
下面跟进StdSchedulerFactory的getScheduler()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
Scheduler getScheduler()
throws
SchedulerException {
if
(cfg ==
null
) {
initialize();
}
SchedulerRepository schedRep = SchedulerRepository.getInstance();
//从"调度器仓库"中根据properties的SchedulerName配置获取一个调度器实例
Scheduler sched = schedRep.lookup(getSchedulerName());
if
(sched !=
null
) {
if
(sched.isShutdown()) {
schedRep.remove(getSchedulerName());
}
else
{
return
sched;
}
}
//初始化调度器
sched = instantiate();
return
sched;
}
|
我们再看一下 sched = instantiate(); 调度器的初始化方法
- 读取配置资源,
- 生成QuartzScheduler对象,
- 创建该对象的运行线程,并启动线程;
- 初始化JobStore,QuartzScheduler,DBConnectionManager等重要组件,
- 至此,调度器的初始化工作已完成,初始化工作中quratz读取了数据库中存放的对应当前调度器的锁信息,对应CRM中的表QRTZ2_LOCKS,中的STATE_ACCESS,TRIGGER_ACCESS两个LOCK_NAME.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public
void
initialize(ClassLoadHelper loadHelper,
SchedulerSignaler signaler)
throws
SchedulerConfigException {
if
(dsName ==
null
) {
throw
new
SchedulerConfigException(
"DataSource name not set."
);
}
classLoadHelper = loadHelper;
if
(isThreadsInheritInitializersClassLoadContext()) {
log.info(
"JDBCJobStore threads will inherit ContextClassLoader of thread: "
+ Thread.currentThread().getName());
initializersLoader = Thread.currentThread().getContextClassLoader();
}
this
.schedSignaler = signaler;
// If the user hasn't specified an explicit lock handler, then
// choose one based on CMT/Clustered/UseDBLocks.
if
(getLockHandler() ==
null
) {
// If the user hasn't specified an explicit lock handler,
// then we *must* use DB locks with clustering
if
(isClustered()) {
setUseDBLocks(
true
);
}
if
(getUseDBLocks()) {
if
(getDriverDelegateClass() !=
null
&& getDriverDelegateClass().equals(MSSQLDelegate.
class
.getName())) {
if
(getSelectWithLockSQL() ==
null
) {
//读取数据库LOCKS表中对应当前调度器的锁信息
String msSqlDflt =
"SELECT * FROM {0}LOCKS WITH (UPDLOCK,ROWLOCK) WHERE "
+ COL_SCHEDULER_NAME +
" = {1} AND LOCK_NAME = ?"
;
getLog().info(
"Detected usage of MSSQLDelegate class - defaulting 'selectWithLockSQL' to '"
+ msSqlDflt +
"'."
);
setSelectWithLockSQL(msSqlDflt);
}
}
getLog().info(
"Using db table-based data access locking (synchronization)."
);
setLockHandler(
new
StdRowLockSemaphore(getTablePrefix(), getInstanceName(), getSelectWithLockSQL()));
}
else
{
getLog().info(
"Using thread monitor-based data access locking (synchronization)."
);
setLockHandler(
new
SimpleSemaphore());
}
}
}
|
当调用sch.start();方法时, scheduler做了如下工作:
1.通知listener开始启动
2.启动调度器线程
3.启动plugin
4.通知listener启动完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
void
start()
throws
SchedulerException {
if
(shuttingDown|| closed) {
throw
new
SchedulerException(
"The Scheduler cannot be restarted after shutdown() has been called."
);
}
// QTZ-212 : calling new schedulerStarting() method on the listeners
// right after entering start()
//通知该调度器的listener启动开始
notifySchedulerListenersStarting();
if
(initialStart ==
null
) {
initialStart =
new
Date();
//启动调度器的线程
this
.resources.getJobStore().schedulerStarted();
//启动plugins
startPlugins();
}
else
{
resources.getJobStore().schedulerResumed();
}
schedThread.togglePause(
false
);
getLog().info(
"Scheduler "
+ resources.getUniqueIdentifier() +
" started."
);
//通知该调度器的listener启动完成
notifySchedulerListenersStarted();
}
|
调度过程
调度器启动后,调度器的线程就处于运行状态了,开始执行quartz的主要工作–调度任务. 前面已介绍过,任务的调度过程大致分为三步:
1.获取待触发trigger
2.触发trigger
3.实例化并执行Job
而调度过程的三个步骤就承载在QuartzSchedulerThread这个调度器线程类的run()方法中。代码大家可以看一下源码。我在这里就略过性的支出run()方法中对应上面三个步骤的源码。
触发器的获取,run()方法中获取待触发trigger
调用JobStoreSupport.acquireNextTriggers方法:
1
2
3
4
5
|
//调度器在trigger队列中寻找30秒内一定数目的trigger准备执行调度,
//参数1:nolaterthan = now+3000ms,参数2 最大获取数量,大小取线程池线程剩余量与定义值得较小者
//参数3 时间窗口 默认为0,程序会在nolaterthan后加上窗口大小来选择trigger
triggers = qsRsrcs.getJobStore().acquireNextTriggers(
now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
|
参数的意义如下:
参数1:nolaterthan = now+3000ms,即未来30s内将会被触发.
参数2 最大获取数量,大小取线程池线程剩余量与定义值得较小者.
参数3 时间窗口 默认为0,程序会在nolaterthan后加上窗口大小来选择
触发trigger: QuartzSchedulerThread.run()
调用JobStoreSupport.triggersFired方法:
1
|
List
|
该方法做了以下工作:
1.获取trigger当前状态
2.通过trigger中的JobKey读取trigger包含的Job信息
3.将trigger更新至触发状态
4.结合calendar的信息触发trigger,涉及多次状态更新
5.更新数据库中trigger的信息,包括更改状态至STATE_COMPLETE,及计算下一次触发时间.
6.返回trigger触发结果的数据传输类TriggerFiredBundle
Job执行过程:QuartzSchedulerThread.run()
1
2
|
qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
shell.initialize(qs);
|