• 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, " 	",
                        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;
        }
    

      

  • 相关阅读:
    Linux中Elasticsearch集群部署
    在.net core 项目中添加swagger支持
    ASP.NET Core 类库中取读配置文件
    Redis的服务安装步骤
    Lambda表达式多表连接的左连
    Angular7 学习资料
    Docker在eShopOnContainer项目中的使用方法
    绘图工具
    万能分页存储过程(SQL Server)
    为什么程序开发人员这么累?
  • 原文地址:https://www.cnblogs.com/wuxinliulei/p/5176162.html
Copyright © 2020-2023  润新知