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 对象的。

/**
     * <p>
     * Returns a handle to the Scheduler produced by this factory.
     * </p>
     *
     * <p>
     * If one of the <code>initialize</code> methods has not be previously
     * called, then the default (no-arg) <code>initialize()</code> method
     * will be called by this method.
     * </p>
     */
    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内置的默认文件来配置

  

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

# 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);

 /**
     * <p>
     * Calls the equivalent method on the 'proxied' <code>QuartzScheduler</code>,
     * passing the <code>SchedulingContext</code> associated with this
     * instance.
     * </p>
     */
    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对象的方法

/**
     * <p>
     * Store the given <code>{@link org.quartz.JobDetail}</code> and <code>{@link org.quartz.Trigger}</code>.
     * </p>
     * 
     * @param newJob
     *          The <code>JobDetail</code> to be stored.
     * @param newTrigger
     *          The <code>Trigger</code> to be stored.
     * @throws ObjectAlreadyExistsException
     *           if a <code>Job</code> 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;
    }

  

在这个调用栈当中,我们看到通过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从而完成任务的调动。

看到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;
    }

  

posted @ 2016-02-01 19:46  无心流泪  阅读(2560)  评论(0编辑  收藏  举报