Quartz进行任务调度时通常会要求一个任务禁止并发执行,此时只需要在Job类上面添加一个注解@DisallowConcurrentExecution
即可。在保存到数据库里面时,对应QRTZ_JOB_DETAILS
表中的IS_NONCONCURRENT
字段的值为1(true)。那么这里是怎么控制的呢?
在Quartz项目搭建与任务执行源码分析中详细介绍一个正常流程过程中涉及的表和状态的变化过程。总结的表格如下
表名 | scheduleJob | acquireNextTriggers | triggersFired | triggeredJobComplete |
---|---|---|---|---|
QRTZ_TRIGGERS | WAITING | (CAS)ACQUIRED | WAITING | WAITING |
QRTZ_FIRED_TRIGGERS | ACQUIRED | EXECUTING |
而对于禁止并发的任务,其状态则如下所示
表名 | scheduleJob | acquireNextTriggers | triggersFired | triggeredJobComplete |
---|---|---|---|---|
QRTZ_TRIGGERS | WAITING | (CAS)ACQUIRED | BLOCKED | WAITING |
QRTZ_FIRED_TRIGGERS | ACQUIRED | EXECUTING |
acquiredJobKeysForNoConcurrentExec
集合保证唯一性)org.quartz.impl.jdbcjobstore.JobStoreSupport#acquireNextTrigger
Set<JobKey> acquiredJobKeysForNoConcurrentExec = new HashSet<JobKey>();
// ... 其他代码省略
// If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then
// put it back into the timeTriggers set and continue to search for next trigger.
JobKey jobKey = nextTrigger.getJobKey();
JobDetail job = getDelegate().selectJobDetail(conn, jobKey, getClassLoadHelper());
if (job.isConcurrentExectionDisallowed()) {
if (acquiredJobKeysForNoConcurrentExec.contains(jobKey)) {
continue; // next trigger
} else {
acquiredJobKeysForNoConcurrentExec.add(jobKey);
}
}
org.quartz.impl.jdbcjobstore.JobStoreSupport#triggersFired
if (job.isConcurrentExectionDisallowed()) {
state = STATE_BLOCKED;
force = false;
try {
getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(),
STATE_BLOCKED, STATE_WAITING);
getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(),
STATE_BLOCKED, STATE_ACQUIRED);
getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(),
STATE_PAUSED_BLOCKED, STATE_PAUSED);
} catch (SQLException e) {
throw new JobPersistenceException(
"Couldn't update states of blocked triggers: "
+ e.getMessage(), e);
}
}
org.quartz.core.QuartzScheduler#notifyJobStoreJobComplete
执行org.quartz.spi.JobStore#triggeredJobComplete
方法
if (jobDetail.isConcurrentExectionDisallowed()) {
getDelegate().updateTriggerStatesForJobFromOtherState(conn,
jobDetail.getKey(), STATE_WAITING,
STATE_BLOCKED);
getDelegate().updateTriggerStatesForJobFromOtherState(conn,
jobDetail.getKey(), STATE_PAUSED,
STATE_PAUSED_BLOCKED);
signalSchedulingChangeOnTxCompletion(0L);
}
除了以上地方,在Quartz启动时,org.quartz.impl.jdbcjobstore.JobStoreSupport#recoverJobs
首先会将一些阻塞的任务都修改为WAITING
,防止系统崩溃导致任务未执行完而来不及恢复禁止并发任务的状态。
// update inconsistent job states
int rows = getDelegate().updateTriggerStatesFromOtherStates(conn,
STATE_WAITING, STATE_ACQUIRED, STATE_BLOCKED);
rows += getDelegate().updateTriggerStatesFromOtherStates(conn,
STATE_PAUSED, STATE_PAUSED_BLOCKED, STATE_PAUSED_BLOCKED);
getLog().info(
"Freed " + rows
+ " triggers from 'acquired' / 'blocked' state.");
另外在项目启动、新增触发器、定时任务补偿等所有涉及数据库操作Trigger中都会执行org.quartz.impl.jdbcjobstore.JobStoreSupport#storeTrigger
操作,而其中都会检查状态,checkBlockedState
.
if (job.isConcurrentExectionDisallowed() && !recovering) {
state = checkBlockedState(conn, job.getKey(), state);
}
if (existingTrigger) {
getDelegate().updateTrigger(conn, newTrigger, state, job);
} else {
getDelegate().insertTrigger(conn, newTrigger, state, job);
}
而checkBlockedState
中,就会根据是否禁止并发并判断是否任务已经执行(通过selectFiredTriggerRecordsByJob
查询QRTZ_FIRED_TRIGGERS
表中是否有数据)返回BLOCKED状态,org.quartz.impl.jdbcjobstore.JobStoreSupport#checkBlockedState
。
/**
* Determines if a Trigger for the given job should be blocked.
* State can only transition to STATE_PAUSED_BLOCKED/BLOCKED from
* PAUSED/STATE_WAITING respectively.
*
* @return STATE_PAUSED_BLOCKED, BLOCKED, or the currentState.
*/
protected String checkBlockedState(
Connection conn, JobKey jobKey, String currentState)
throws JobPersistenceException {
// State can only transition to BLOCKED from PAUSED or WAITING.
if ((!currentState.equals(STATE_WAITING)) &&
(!currentState.equals(STATE_PAUSED))) {
return currentState;
}
try {
List<FiredTriggerRecord> lst = getDelegate().selectFiredTriggerRecordsByJob(conn,
jobKey.getName(), jobKey.getGroup());
if (lst.size() > 0) {
FiredTriggerRecord rec = lst.get(0);
if (rec.isJobDisallowsConcurrentExecution()) { // OLD_TODO: worry about failed/recovering/volatile job states?
return (STATE_PAUSED.equals(currentState)) ? STATE_PAUSED_BLOCKED : STATE_BLOCKED;
}
}
return currentState;
} catch (SQLException e) {
throw new JobPersistenceException(
"Couldn't determine if trigger should be in a blocked state '"
+ jobKey + "': "
+ e.getMessage(), e);
}
}
从上面可以看到,基本上在整个操作触发器的生命周期,都会考虑当前任务是否禁止并发操作,通过QRTZ_FIRED_TRIGGERS
表是否有数据判断任务是否执行、不支持并发机制的任务在执行时将QRTZ_TRIGGERS
中的状态改为BLOCKED
以避免再次被触发。