java quartz 源码_Quartz源码阅读

基于Quartz1.8.5的源码解读

首先看一个demo

//简单的任务管理类

//QuartzManager.java

package quartzPackage;

import java.text.ParseException;

import org.quartz.CronTrigger;

import org.quartz.Job;

import org.quartz.JobDetail;

import org.quartz.Scheduler;

import org.quartz.SchedulerException;

import org.quartz.SchedulerFactory;

import org.quartz.Trigger;

import org.quartz.impl.StdSchedulerFactory;

/** *//**

* @Title:Quartz管理类

*

* @Description:

*

* @Copyright:

* @author zz 2008-10-8 14:19:01

* @version 1.00.000

*

*/

public class QuartzManager {

private static SchedulerFactory sf = new StdSchedulerFactory();

private static String JOB_GROUP_NAME = "group1";

private static String TRIGGER_GROUP_NAME = "trigger1";

/** *//**

* 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名

* @param jobName 任务名

* @param job 任务

* @param time 时间设置,参考quartz说明文档

* @throws SchedulerException

* @throws ParseException

*/

public static void addJob(String jobName,Job job,String time)

throws SchedulerException, ParseException{

Scheduler sched = sf.getScheduler();

JobDetail jobDetail = new JobDetail(jobName, JOB_GROUP_NAME, job.getClass());//任务名,任务组,任务执行类

//触发器

CronTrigger trigger =

new CronTrigger(jobName, TRIGGER_GROUP_NAME);//触发器名,触发器组

trigger.setCronExpression(time);//触发器时间设定

sched.scheduleJob(jobDetail,trigger);

//启动

if(!sched.isShutdown())

sched.start();

}

/** *//**

* 添加一个定时任务

* @param jobName 任务名

* @param jobGroupName 任务组名

* @param triggerName 触发器名

* @param triggerGroupName 触发器组名

* @param job 任务

* @param time 时间设置,参考quartz说明文档

* @throws SchedulerException

* @throws ParseException

*/

public static void addJob(String jobName,String jobGroupName,

String triggerName,String triggerGroupName,

Job job,String time)

throws SchedulerException, ParseException{

Scheduler sched = sf.getScheduler();

JobDetail jobDetail = new JobDetail(jobName, jobGroupName, job.getClass());//任务名,任务组,任务执行类

//触发器

CronTrigger trigger =

new CronTrigger(triggerName, triggerGroupName);//触发器名,触发器组

trigger.setCronExpression(time);//触发器时间设定

sched.scheduleJob(jobDetail,trigger);

if(!sched.isShutdown())

sched.start();

}

/** *//**

* 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)

* @param jobName

* @param time

* @throws SchedulerException

* @throws ParseException

*/

public static void modifyJobTime(String jobName,String time)

throws SchedulerException, ParseException{

Scheduler sched = sf.getScheduler();

Trigger trigger = sched.getTrigger(jobName,TRIGGER_GROUP_NAME);

if(trigger != null){

CronTrigger ct = (CronTrigger)trigger;

ct.setCronExpression(time);

sched.resumeTrigger(jobName,TRIGGER_GROUP_NAME);

}

}

/** *//**

* 修改一个任务的触发时间

* @param triggerName

* @param triggerGroupName

* @param time

* @throws SchedulerException

* @throws ParseException

*/

public static void modifyJobTime(String triggerName,String triggerGroupName,

String time)

throws SchedulerException, ParseException{

Scheduler sched = sf.getScheduler();

Trigger trigger = sched.getTrigger(triggerName,triggerGroupName);

if(trigger != null){

CronTrigger ct = (CronTrigger)trigger;

//修改时间

ct.setCronExpression(time);

//重启触发器

sched.resumeTrigger(triggerName,triggerGroupName);

}

}

/** *//**

* 移除一个任务(使用默认的任务组名,触发器名,触发器组名)

* @param jobName

* @throws SchedulerException

*/

public static void removeJob(String jobName)

throws SchedulerException{

Scheduler sched = sf.getScheduler();

sched.pauseTrigger(jobName,TRIGGER_GROUP_NAME);//停止触发器

sched.unscheduleJob(jobName,TRIGGER_GROUP_NAME);//移除触发器

sched.deleteJob(jobName,JOB_GROUP_NAME);//删除任务

}

/** *//**

* 移除一个任务

* @param jobName

* @param jobGroupName

* @param triggerName

* @param triggerGroupName

* @throws SchedulerException

*/

public static void removeJob(String jobName,String jobGroupName,

String triggerName,String triggerGroupName)

throws SchedulerException{

Scheduler sched = sf.getScheduler();

sched.pauseTrigger(triggerName,triggerGroupName);//停止触发器

sched.unscheduleJob(triggerName,triggerGroupName);//移除触发器

sched.deleteJob(jobName,jobGroupName);//删除任务

}

}

//测试main函数

//QuartzTest.java

package quartzPackage;

import java.text.SimpleDateFormat;

import java.util.Date;

public class QuartzTest {

/** *//**

* @param args

*/

public static void main(String[] args) {

// TODO Auto-generated method stub

SimpleDateFormat DateFormat = new SimpleDateFormat("yyyyMMddHHmmss");

Date d = new Date();

String returnstr = DateFormat.format(d);

TestJob job = new TestJob();

String job_name ="11";

try {

System.out.println(returnstr+ "【系统启动】");

QuartzManager.addJob(job_name,job,"0/2 * * * * ?"); //每2秒钟执行一次

// Thread.sleep(10000);

// System.out.println("【修改时间】");

// QuartzManager.modifyJobTime(job_name,"0/10 * * * * ?");

// Thread.sleep(20000);

// System.out.println("【移除定时】");

// QuartzManager.removeJob(job_name);

// Thread.sleep(10000);

//

// System.out.println("/n【添加定时任务】");

// QuartzManager.addJob(job_name,job,"0/5 * * * * ?");

} catch (Exception e) {

e.printStackTrace();

}

}

}

//测试工作类

//TestJob.java

package quartzPackage;

import java.text.SimpleDateFormat;

import java.util.Date;

import org.quartz.Job;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

public class TestJob implements Job {

SimpleDateFormat DateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Date d = new Date();

String returnstr = DateFormat.format(d);

public void execute(JobExecutionContext arg0) throws JobExecutionException {

// TODO Auto-generated method stub

System.out.println(returnstr+"★★★★★★★★★★★");

}

}

先说明一下几个重要的Quartz组件:

1.scheduler是一个计划调度器容器(总部),容器里面可以盛放众多的JobDetail和trigger,当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行。

①Scheduler对象的产生过程 Scheduler对象是通过SchedulerFactory对象的getScheduler方法产生的,org.quartz.SchedulerFactory工厂接口有两个具体实现,一个是org.quartz.impl.StdSchedulerFactory对象, 一个是org.quartz.impl.DirectSchedulerFactory对象,二者的区别是什么?暂时没有分析

private static SchedulerFactory sf = new StdSchedulerFactory();

我们栗子当中使用的是是org.quartz.impl.StdSchedulerFactory对象,我们来看一下StdSchedulerFactory对象是如何产生Scheduler 对象的。

/**

*

* Returns a handle to the Scheduler produced by this factory.

*

*

*

* If one of the initialize methods has not be previously

* called, then the default (no-arg) initialize() method

* will be called by this method.

*

*/

public Scheduler getScheduler() throws SchedulerException {

if (cfg == null) {

initialize();

}

SchedulerRepository schedRep = SchedulerRepository.getInstance();

Scheduler sched = schedRep.lookup(getSchedulerName());

if (sched != null) {

if (sched.isShutdown()) {

schedRep.remove(getSchedulerName());

} else {

return sched;

}

}

sched = instantiate();

return sched;

}

跟进初始化调度器方法sched = instantiate();发现是一个700多行的初始化方法,涉及到

读取配置资源,

生成QuartzScheduler对象,

创建该对象的运行线程,并启动线程;

初始化JobStore,QuartzScheduler,DBConnectionManager等重要组件,

至此,调度器的初始化工作已完成,初始化工作中quratz读取了数据库中存放的对应当前调度器的锁信息,对应CRM中的表QRTZ2_LOCKS,中的STATE_ACCESS,TRIGGER_ACCESS两个LOCK_NAME.

public void initialize() throws SchedulerException {

// short-circuit if already initialized

if (cfg != null) {

return;

}

if (initException != null) {

throw initException;

}

String requestedFile = System.getProperty(PROPERTIES_FILE);

String propFileName = requestedFile != null ? requestedFile

: "quartz.properties";

File propFile = new File(propFileName);

Properties props = new Properties();

InputStream in = null;

try {

if (propFile.exists()) {

try {

if (requestedFile != null) {

propSrc = "specified file: '" + requestedFile + "'";

} else {

propSrc = "default file in current working dir: 'quartz.properties'";

}

in = new BufferedInputStream(new FileInputStream(propFileName));

props.load(in);

} catch (IOException ioe) {

initException = new SchedulerException("Properties file: '"

+ propFileName + "' could not be read.", ioe);

throw initException;

}

} else if (requestedFile != null) {

in =

Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile);

if(in == null) {

initException = new SchedulerException("Properties file: '"

+ requestedFile + "' could not be found.");

throw initException;

}

propSrc = "specified file: '" + requestedFile + "' in the class resource path.";

in = new BufferedInputStream(in);

try {

props.load(in);

} catch (IOException ioe) {

initException = new SchedulerException("Properties file: '"

+ requestedFile + "' could not be read.", ioe);

throw initException;

}

} else {

propSrc = "default resource file in Quartz package: 'quartz.properties'";

ClassLoader cl = getClass().getClassLoader();

if(cl == null)

cl = findClassloader();

if(cl == null)

throw new SchedulerConfigException("Unable to find a class loader on the current thread or class.");

in = cl.getResourceAsStream(

"quartz.properties");

if (in == null) {

in = cl.getResourceAsStream(

"/quartz.properties");

}

if (in == null) {

in = cl.getResourceAsStream(

"org/quartz/quartz.properties");

}

if (in == null) {

initException = new SchedulerException(

"Default quartz.properties not found in class path");

throw initException;

}

try {

props.load(in);

} catch (IOException ioe) {

initException = new SchedulerException(

"Resource properties file: 'org/quartz/quartz.properties' "

+ "could not be read from the classpath.", ioe);

throw initException;

}

}

} finally {

if(in != null) {

try { in.close(); } catch(IOException ignore) { /* ignore */ }

}

}

initialize(overrideWithSysProps(props));

}

这里牵涉到读取配置文件,由于程序没有配置文件,将读取quartz内置的默认文件来配置

1ef6a017baff7de460ed6861d1df2ab5.png

这个默认配置文件内容如下

# Default Properties file for use by StdSchedulerFactory

# to create a Quartz Scheduler Instance, if a different

# properties file is not explicitly specified.

#

org.quartz.scheduler.instanceName = DefaultQuartzScheduler

org.quartz.scheduler.rmi.export = false

org.quartz.scheduler.rmi.proxy = false

org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadCount = 10

org.quartz.threadPool.threadPriority = 5

org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

2.Trigger对象

Trigger代表一个调度参数的配置,什么时候去调,Job的Name 和group将唯一标识一个Trigger,该Trigger对象将会和JobDetail对象一起保存到相应的执行任务的缓存或者数据库中,显然我们的程序没有使用到数据库配置,我们的所有执行任务都被缓存到

org.quartz.simpl.RAMJobStore 这个类当中 ,该类实现接口org.quartz.spi.JobStore类,

事实上Trigger对象和Job对象一一对象,Job对象被封装到JobDetail对象当中,全部被保存到RAMJobStore对象当中,看到其中有这个缓存对象

protected TreeSet timeTriggers = new TreeSet(new TriggerComparator());

class TriggerComparator implements Comparator {

public int compare(Object obj1, Object obj2) {

TriggerWrapper trig1 = (TriggerWrapper) obj1;

TriggerWrapper trig2 = (TriggerWrapper) obj2;

int comp = trig1.trigger.compareTo(trig2.trigger);

if (comp != 0) {

return comp;

}

comp = trig2.trigger.getPriority() - trig1.trigger.getPriority();

if (comp != 0) {

return comp;

}

return trig1.trigger.getFullName().compareTo(trig2.trigger.getFullName());

}

public boolean equals(Object obj) {

return (obj instanceof TriggerComparator);

}

}

该比较顺序的方法将Trigger对象的执行时间->优先级信息->名字信息比较排序,每次要获取要被执行的Trigger对象的时候都拿到TreeMap对象的头部对象peek对象。

我们看下Job对象和Trigger对象是如何加入到RAMJobStore对象当中去的

Scheduler sched = sf.getScheduler();

sched.scheduleJob(jobDetail,trigger);

/**

*

* Calls the equivalent method on the 'proxied' QuartzScheduler,

* passing the SchedulingContext associated with this

* instance.

*

*/

public Date scheduleJob(JobDetail jobDetail, Trigger trigger)

throws SchedulerException {

return sched.scheduleJob(schedCtxt, jobDetail, trigger);

}

在org.quartz.core.QuartzScheduler对象当中的方法scheduleJob方法

public Date scheduleJob(SchedulingContext ctxt, JobDetail jobDetail,

Trigger trigger) throws SchedulerException {

validateState();

if (jobDetail == null) {

throw new SchedulerException("JobDetail cannot be null",

SchedulerException.ERR_CLIENT_ERROR);

}

if (trigger == null) {

throw new SchedulerException("Trigger cannot be null",

SchedulerException.ERR_CLIENT_ERROR);

}

jobDetail.validate();

if (trigger.getJobName() == null) {

trigger.setJobName(jobDetail.getName());

trigger.setJobGroup(jobDetail.getGroup());

} else if (trigger.getJobName() != null

&& !trigger.getJobName().equals(jobDetail.getName())) {

throw new SchedulerException(

"Trigger does not reference given job!",

SchedulerException.ERR_CLIENT_ERROR);

} else if (trigger.getJobGroup() != null

&& !trigger.getJobGroup().equals(jobDetail.getGroup())) {

throw new SchedulerException(

"Trigger does not reference given job!",

SchedulerException.ERR_CLIENT_ERROR);

}

trigger.validate();

Calendar cal = null;

if (trigger.getCalendarName() != null) {

cal = resources.getJobStore().retrieveCalendar(ctxt,

trigger.getCalendarName());

}

Date ft = trigger.computeFirstFireTime(cal);

if (ft == null) {

throw new SchedulerException(

"Based on configured schedule, the given trigger will never fire.",

SchedulerException.ERR_CLIENT_ERROR);

}

resources.getJobStore().storeJobAndTrigger(ctxt, jobDetail, trigger);

notifySchedulerListenersJobAdded(jobDetail);

notifySchedulerThread(trigger.getNextFireTime().getTime());

notifySchedulerListenersSchduled(trigger);

return ft;

}

处理jobDetail对象和trigger对象的方法 resources.getJobStore().storeJobAndTrigger(ctxt, jobDetail, trigger);

可以看到将会调用RAMJobStore对象的方法

/**

*

* Store the given {@link org.quartz.JobDetail} and {@link org.quartz.Trigger}.

*

*

* @param newJob

* The JobDetail to be stored.

* @param newTrigger

* The Trigger to be stored.

* @throws ObjectAlreadyExistsException

* if a Job with the same name/group already

* exists.

*/

public void storeJobAndTrigger(SchedulingContext ctxt, JobDetail newJob,

Trigger newTrigger) throws JobPersistenceException {

storeJob(ctxt, newJob, false);

storeTrigger(ctxt, newTrigger, false);

}

看到storeJob方法

public void storeJob(SchedulingContext ctxt, JobDetail newJob,

boolean replaceExisting) throws ObjectAlreadyExistsException {

JobWrapper jw = new JobWrapper((JobDetail)newJob.clone());

boolean repl = false;

synchronized (lock) {

if (jobsByFQN.get(jw.key) != null) {

if (!replaceExisting) {

throw new ObjectAlreadyExistsException(newJob);

}

repl = true;

}

if (!repl) {

// get job group

HashMap grpMap = (HashMap) jobsByGroup.get(newJob.getGroup());

if (grpMap == null) {

grpMap = new HashMap(100);

jobsByGroup.put(newJob.getGroup(), grpMap);

}

// add to jobs by group

grpMap.put(newJob.getName(), jw);

// add to jobs by FQN map

jobsByFQN.put(jw.key, jw);

} else {

// update job detail

JobWrapper orig = (JobWrapper) jobsByFQN.get(jw.key);

orig.jobDetail = jw.jobDetail; // already cloned

}

}

}

我们看到JobWrapper对象,将JobDetail对象包装起来

class JobWrapper {

public String key;

public JobDetail jobDetail;

JobWrapper(JobDetail jobDetail) {

this.jobDetail = jobDetail;

key = getJobNameKey(jobDetail);

}

JobWrapper(JobDetail jobDetail, String key) {

this.jobDetail = jobDetail;

this.key = key;

}

static String getJobNameKey(JobDetail jobDetail) {

return jobDetail.getGroup() + "_$x$x$_" + jobDetail.getName();

}

static String getJobNameKey(String jobName, String groupName) {

return groupName + "_$x$x$_" + jobName;

}

public boolean equals(Object obj) {

if (obj instanceof JobWrapper) {

JobWrapper jw = (JobWrapper) obj;

if (jw.key.equals(this.key)) {

return true;

}

}

return false;

}

public int hashCode() {

return key.hashCode();

}

}

key是有jobDetail的组名和jobDetail的name组合在一起的

QuartzManager.addJob("11",job,"0/2 * * * * ?"); //每2秒钟执行一次

private static String JOB_GROUP_NAME = "group1";

private static String TRIGGER_GROUP_NAME = "trigger1";

/** *//**

* 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名

* @param jobName 任务名

* @param job 任务

* @param time 时间设置,参考quartz说明文档

* @throws SchedulerException

* @throws ParseException

*/

public static void addJob(String jobName,Job job,String time)

throws SchedulerException, ParseException{

Scheduler sched = sf.getScheduler();

JobDetail jobDetail = new JobDetail(jobName, JOB_GROUP_NAME, job.getClass());//任务名,任务组,任务执行类

//触发器

CronTrigger trigger =

new CronTrigger(jobName, TRIGGER_GROUP_NAME);//触发器名,触发器组

trigger.setCronExpression(time);//触发器时间设定

sched.scheduleJob(jobDetail,trigger);

//启动

if(!sched.isShutdown())

sched.start();

}

JobDetail的名字和组名都是我们确定的,Trigger的名字和组名也是我们确定的。

storeTrigger方法

public void storeTrigger(SchedulingContext ctxt, Trigger newTrigger,

boolean replaceExisting) throws JobPersistenceException {

TriggerWrapper tw = new TriggerWrapper((Trigger)newTrigger.clone());

synchronized (lock) {

if (triggersByFQN.get(tw.key) != null) {

if (!replaceExisting) {

throw new ObjectAlreadyExistsException(newTrigger);

}

removeTrigger(ctxt, newTrigger.getName(), newTrigger.getGroup(), false);

}

if (retrieveJob(ctxt, newTrigger.getJobName(), newTrigger.getJobGroup()) == null) {

throw new JobPersistenceException("The job ("

+ newTrigger.getFullJobName()

+ ") referenced by the trigger does not exist.");

}

// add to triggers array

triggers.add(tw);

// add to triggers by group

HashMap grpMap = (HashMap) triggersByGroup.get(newTrigger

.getGroup());

if (grpMap == null) {

grpMap = new HashMap(100);

triggersByGroup.put(newTrigger.getGroup(), grpMap);

}

grpMap.put(newTrigger.getName(), tw);

// add to triggers by FQN map

triggersByFQN.put(tw.key, tw);

if (pausedTriggerGroups.contains(newTrigger.getGroup())

|| pausedJobGroups.contains(newTrigger.getJobGroup())) {

tw.state = TriggerWrapper.STATE_PAUSED;

if (blockedJobs.contains(tw.jobKey)) {

tw.state = TriggerWrapper.STATE_PAUSED_BLOCKED;

}

} else if (blockedJobs.contains(tw.jobKey)) {

tw.state = TriggerWrapper.STATE_BLOCKED;

} else {

timeTriggers.add(tw);

}

}

}

我们在配置job的时间的时候采用的是字符串形式

字段允许值允许的特殊字符

秒 0-59 , - * /

分 0-59 , - * /

小时 0-23 , - * /

日期 1-31 , - * ? / L W C

月份 1-12 或者 JAN-DEC , - * /

星期 1-7 或者 SUN-SAT , - * ? / L C #

年(可选)留空, 1970-2099 , - * /

表达式意义

"0 0 12 * * ?" 每天中午12点触发

"0 15 10 ? * *" 每天上午10:15触发

"0 15 10 * * ?" 每天上午10:15触发

"0 15 10 * * ? *" 每天上午10:15触发

"0 15 10 * * ? 2005" 2005年的每天上午10:15触发

"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发

"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发

"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发

"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发

"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发

"0 15 10 15 * ?" 每月15日上午10:15触发

"0 15 10 L * ?" 每月最后一日的上午10:15触发

"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发

"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发

"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

每天早上6点

0 6 * * *

每两个小时

0 */2 * * *

晚上11点到早上7点之间每两个小时,早上八点

0 23-7/2,8 * * *

每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点

0 11 4 * 1-3

1月1日早上4点

0 4 1 1 *

这个时间具体是怎么解析的呢?

//触发器

CronTrigger trigger =

new CronTrigger(jobName, TRIGGER_GROUP_NAME);//触发器名,触发器组

trigger.setCronExpression(time);//触发器时间设定

重点在trigger的setCronExpression(time)上,这里面对string的时间串进行了解析,将标准的下次执行时间进行了设置。

org.quartz.CronTrigger类当中

public void setCronExpression(String cronExpression) throws ParseException {

TimeZone origTz = getTimeZone();

this.cronEx = new CronExpression(cronExpression);

this.cronEx.setTimeZone(origTz);

}

在org.quartz.CronExpression类当中

public CronExpression(String cronExpression) throws ParseException {

if (cronExpression == null) {

throw new IllegalArgumentException("cronExpression cannot be null");

}

this.cronExpression = cronExpression.toUpperCase(Locale.US);

buildExpression(this.cronExpression);

}

protected void buildExpression(String expression) throws ParseException {

expressionParsed = true;

try {

if (seconds == null) {

seconds = new TreeSet();

}

if (minutes == null) {

minutes = new TreeSet();

}

if (hours == null) {

hours = new TreeSet();

}

if (daysOfMonth == null) {

daysOfMonth = new TreeSet();

}

if (months == null) {

months = new TreeSet();

}

if (daysOfWeek == null) {

daysOfWeek = new TreeSet();

}

if (years == null) {

years = new TreeSet();

}

int exprOn = SECOND;

StringTokenizer exprsTok = new StringTokenizer(expression, " \t",

false);

while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {

String expr = exprsTok.nextToken().trim();

// throw an exception if L is used with other days of the month

if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) {

throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);

}

// throw an exception if L is used with other days of the week

if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.indexOf(",") >= 0) {

throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);

}

StringTokenizer vTok = new StringTokenizer(expr, ",");

while (vTok.hasMoreTokens()) {

String v = vTok.nextToken();

storeExpressionVals(0, v, exprOn);

}

exprOn++;

}

if (exprOn <= DAY_OF_WEEK) {

throw new ParseException("Unexpected end of expression.",

expression.length());

}

if (exprOn <= YEAR) {

storeExpressionVals(0, "*", YEAR);

}

TreeSet dow = getSet(DAY_OF_WEEK);

TreeSet dom = getSet(DAY_OF_MONTH);

// Copying the logic from the UnsupportedOperationException below

boolean dayOfMSpec = !dom.contains(NO_SPEC);

boolean dayOfWSpec = !dow.contains(NO_SPEC);

if (dayOfMSpec && !dayOfWSpec) {

// skip

} else if (dayOfWSpec && !dayOfMSpec) {

// skip

} else {

throw new ParseException(

"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);

}

} catch (ParseException pe) {

throw pe;

} catch (Exception e) {

throw new ParseException("Illegal cron expression format ("

+ e.toString() + ")", 0);

}

}

-------------------------------------------------------------------------------------------------------------------------------------

当我们把JobDetail和Trigger对象都保存到相应的位置之后,是谁来触发他们的呢?是通过的org.quartz.core.QuartzSchedulerThread这个处理主线程来实现的 ,可以这么认为,这个线程不断地额扫描RAMJobStore当中的内容,发现有需要执行的Job的时候,就把任务拿出来执行,并且是异步到线程池中去执行,大多数定时器架构都是需要一个不断刷新和派发任务的的线程,一个执行任务的线程池,一个保存定时任务的的对象或者数据库。

那这个线程是什么时候启动的呢?是在我们获取Scheduler对象的时候内部创建并启动的,现在我们看看这个过程。

看我们的

public static void addJob(String jobName,Job job,String time)

throws SchedulerException, ParseException{

Scheduler sched = sf.getScheduler();

JobDetail jobDetail = new JobDetail(jobName, JOB_GROUP_NAME, job.getClass());//任务名,任务组,任务执行类

//触发器

CronTrigger trigger =

new CronTrigger(jobName, TRIGGER_GROUP_NAME);//触发器名,触发器组

trigger.setCronExpression(time);//触发器时间设定

sched.scheduleJob(jobDetail,trigger);

//启动

if(!sched.isShutdown())

sched.start();

}

跟进StdSchedulerFactory.getScheduler()方法内部

public Scheduler getScheduler() throws SchedulerException {

if (cfg == null) {

initialize();

}

SchedulerRepository schedRep = SchedulerRepository.getInstance();

Scheduler sched = schedRep.lookup(getSchedulerName());

if (sched != null) {

if (sched.isShutdown()) {

schedRep.remove(getSchedulerName());

} else {

return sched;

}

}

sched = instantiate();

return sched;

}

46811ef5a9a447915e9770517d9788b9.png

在这个调用栈当中,我们看到通过org.quartz.impl.StdSchedulerFactory类当中的getScheduer()方法最终调用到了org.quartz.core.QuartzSchedulerThread对象的构造方法,而在构造方法当中,我们看到进行了自启动。

QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs,

SchedulingContext ctxt, boolean setDaemon, int threadPrio) {

super(qs.getSchedulerThreadGroup(), qsRsrcs.getThreadName());

this.qs = qs;

this.qsRsrcs = qsRsrcs;

this.ctxt = ctxt;

this.setDaemon(setDaemon);

if(qsRsrcs.isThreadsInheritInitializersClassLoadContext()) {

log.info("QuartzSchedulerThread Inheriting ContextClassLoader of thread: " + Thread.currentThread().getName());

this.setContextClassLoader(Thread.currentThread().getContextClassLoader());

}

this.setPriority(threadPrio);

// start the underlying thread, but put this object into the 'paused'

// state

// so processing doesn't start yet...

paused = true;

halted = new AtomicBoolean(false);

this.start();

}

初始化自身,并且this.start()启动线程。

对于线程的run方法即如何派发任务也是一个看点,该处理动态的采用Object.wait 和notify方法,避免了无效的CPU空转,尤其需要注意的是在wait期间,可以通过加入紧急任务来nitifyAll从而完成任务的调动。

813b1fe468c29580355d4249e38979a0.png

看到QuartzScheduler的addJob方法加入了一个紧急的Job任务

public void run() {

boolean lastAcquireFailed = false;

while (!halted.get()) {

try {

// check if we're supposed to pause...

synchronized (sigLock) {

while (paused && !halted.get()) {

try {

// wait until togglePause(false) is called...

sigLock.wait(1000L);

} catch (InterruptedException ignore) {

}

}

if (halted.get()) {

break;

}

}

int availTreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();

if(availTreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads...

Trigger trigger = null;

long now = System.currentTimeMillis();

clearSignaledSchedulingChange();

try {

trigger = qsRsrcs.getJobStore().acquireNextTrigger(

ctxt, now + idleWaitTime);

lastAcquireFailed = false;

} catch (JobPersistenceException jpe) {

if(!lastAcquireFailed) {

qs.notifySchedulerListenersError(

"An error occured while scanning for the next trigger to fire.",

jpe);

}

lastAcquireFailed = true;

} catch (RuntimeException e) {

if(!lastAcquireFailed) {

getLog().error("quartzSchedulerThreadLoop: RuntimeException "

+e.getMessage(), e);

}

lastAcquireFailed = true;

}

if (trigger != null) {

now = System.currentTimeMillis();

long triggerTime = trigger.getNextFireTime().getTime();

long timeUntilTrigger = triggerTime - now;

while(timeUntilTrigger > 2) {

synchronized(sigLock) {

if(!isCandidateNewTimeEarlierWithinReason(triggerTime, false)) {

try {

// we could have blocked a long while

// on 'synchronize', so we must recompute

now = System.currentTimeMillis();

timeUntilTrigger = triggerTime - now;

if(timeUntilTrigger >= 1)

sigLock.wait(timeUntilTrigger);

} catch (InterruptedException ignore) {

}

}

}

if(releaseIfScheduleChangedSignificantly(trigger, triggerTime)) {

trigger = null;

break;

}

now = System.currentTimeMillis();

timeUntilTrigger = triggerTime - now;

}

if(trigger == null)

continue;

// set trigger to 'executing'

TriggerFiredBundle bndle = null;

boolean goAhead = true;

synchronized(sigLock) {

goAhead = !halted.get();

}

if(goAhead) {

try {

bndle = qsRsrcs.getJobStore().triggerFired(ctxt,

trigger);

} catch (SchedulerException se) {

qs.notifySchedulerListenersError(

"An error occured while firing trigger '"

+ trigger.getFullName() + "'", se);

} catch (RuntimeException e) {

getLog().error(

"RuntimeException while firing trigger " +

trigger.getFullName(), e);

// db connection must have failed... keep

// retrying until it's up...

releaseTriggerRetryLoop(trigger);

}

}

// it's possible to get 'null' if the trigger was paused,

// blocked, or other similar occurrences that prevent it being

// fired at this time... or if the scheduler was shutdown (halted)

if (bndle == null) {

try {

qsRsrcs.getJobStore().releaseAcquiredTrigger(ctxt,

trigger);

} catch (SchedulerException se) {

qs.notifySchedulerListenersError(

"An error occured while releasing trigger '"

+ trigger.getFullName() + "'", se);

// db connection must have failed... keep retrying

// until it's up...

releaseTriggerRetryLoop(trigger);

}

continue;

}

// TODO: improvements:

//

// 2- make sure we can get a job runshell before firing trigger, or

// don't let that throw an exception (right now it never does,

// but the signature says it can).

// 3- acquire more triggers at a time (based on num threads available?)

JobRunShell shell = null;

try {

shell = qsRsrcs.getJobRunShellFactory().borrowJobRunShell();

shell.initialize(qs, bndle);

} catch (SchedulerException se) {

try {

qsRsrcs.getJobStore().triggeredJobComplete(ctxt,

trigger, bndle.getJobDetail(), Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR);

} catch (SchedulerException se2) {

qs.notifySchedulerListenersError(

"An error occured while placing job's triggers in error state '"

+ trigger.getFullName() + "'", se2);

// db connection must have failed... keep retrying

// until it's up...

errorTriggerRetryLoop(bndle);

}

continue;

}

if (qsRsrcs.getThreadPool().runInThread(shell) == false) {

try {

// this case should never happen, as it is indicative of the

// scheduler being shutdown or a bug in the thread pool or

// a thread pool being used concurrently - which the docs

// say not to do...

getLog().error("ThreadPool.runInThread() return false!");

qsRsrcs.getJobStore().triggeredJobComplete(ctxt,

trigger, bndle.getJobDetail(), Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR);

} catch (SchedulerException se2) {

qs.notifySchedulerListenersError(

"An error occured while placing job's triggers in error state '"

+ trigger.getFullName() + "'", se2);

// db connection must have failed... keep retrying

// until it's up...

releaseTriggerRetryLoop(trigger);

}

}

continue;

}

} else { // if(availTreadCount > 0)

continue; // should never happen, if threadPool.blockForAvailableThreads() follows contract

}

long now = System.currentTimeMillis();

long waitTime = now + getRandomizedIdleWaitTime();

long timeUntilContinue = waitTime - now;

synchronized(sigLock) {

try {

sigLock.wait(timeUntilContinue);

} catch (InterruptedException ignore) {

}

}

} catch(RuntimeException re) {

getLog().error("Runtime error occured in main trigger firing loop.", re);

}

} // loop...

// drop references to scheduler stuff to aid garbage collection...

qs = null;

qsRsrcs = null;

}

我们下面来分析下run方法是如何找到要执行的任务,并且派发出去,如何进行wait和notify,如何进行循环任务的处理

下面是org.quartz.simpl.RAMJobStore当中的获取需要调用的Trigger的方法

public Trigger acquireNextTrigger(SchedulingContext ctxt, long noLaterThan) {

TriggerWrapper tw = null;

synchronized (lock) {

while (tw == null) {

try {

tw = (TriggerWrapper) timeTriggers.first();

} catch (java.util.NoSuchElementException nsee) {

return null;

}

if (tw == null) {

return null;

}

if (tw.trigger.getNextFireTime() == null) {

timeTriggers.remove(tw);

tw = null;

continue;

}

timeTriggers.remove(tw);

if (applyMisfire(tw)) {

if (tw.trigger.getNextFireTime() != null) {

timeTriggers.add(tw);

}

tw = null;

continue;

}

if(tw.trigger.getNextFireTime().getTime() > noLaterThan) {

timeTriggers.add(tw);

return null;

}

tw.state = TriggerWrapper.STATE_ACQUIRED;

tw.trigger.setFireInstanceId(getFiredTriggerRecordId());

Trigger trig = (Trigger) tw.trigger.clone();

return trig;

}

}

return null;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值