Quartz框架源码分析——主要组件

Quartz是一个开源的任务调度框架,支持多种触发器类型如SimpleTrigger和CronTrigger,能够灵活地调度任务执行。本文深入介绍了Quartz的核心组件,包括Scheduler、JobStore、ThreadPool等,以及其实现原理和设计模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介:开源任务进度管理器

使用:https://github.com/dufyun/quartz-core-learning QuartzDemo的代码:spring集成quartz;quartz on springboot

一个小Demo:

public class QuartzDemo implements Job{  
  
    @Override  
    public void execute(JobExecutionContext arg0) throws JobExecutionException {  
        System.out.println("Quartz执行.......");  
          
    }  
  
}

/**
 * This is a RAM Store Quartz!
 *
 */
public class RAMQuartz {

	private static Logger _log = LoggerFactory.getLogger(RAMQuartz.class);
	
	public static void main(String[] args) throws SchedulerException {
		//1.创建Scheduler的工厂
		SchedulerFactory sf = new StdSchedulerFactory();
		//2.从工厂中获取调度器实例
		Scheduler scheduler = sf.getScheduler();
		
		
		//3.创建JobDetail
		JobDetail jb = JobBuilder.newJob(RAMJob.class)
				.withDescription("this is a ram job") //job的描述
				.withIdentity("ramJob", "ramGroup") //job 的name和group
				.build();
		
		//任务运行的时间,SimpleSchedle类型触发器有效
		long time=  System.currentTimeMillis() + 3*1000L; //3秒后启动任务
		Date statTime = new Date(time);
		
		//4.创建Trigger
			//使用SimpleScheduleBuilder或者CronScheduleBuilder
		Trigger t = TriggerBuilder.newTrigger()
					.withDescription("")
					.withIdentity("ramTrigger", "ramTriggerGroup")
					//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
					.startAt(statTime)  //默认当前时间启动
					.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //两秒执行一次
					.build();
		
		//5.注册任务和定时器
		scheduler.scheduleJob(jb, t);
		
		//6.启动 调度器
		scheduler.start();
		_log.info("启动时间 : " + new Date());
			
	}
}

         Quartz框架中用到的设计模式:

工厂模式,静态工厂

单例模式:懒汉式,饿汉式(还可以改进)

桥接模式:

quartz的原理

Quartz基础组件:

实现了org.quartz.Scheduler接口的StdSchedule实际只是QuartzScheduler的静态代理,后者实现了Scheduler所有操作。

(一)ScheduleFactory:Schedule的创建

  • 从StdScheduleFactory来看,这个工厂类做了什么?
  1. 读取配置文件, 配置文件中需要配置scheduler、线程池(JobPool)、jobStore、jobListener、triggerListenner、插件等。配置文件的读取过程如下:
    读取参数系统参数(启动参数)System中配置的org.quartz.properties指定的文件;
    如果找不到则读取项目Classpath目录下的quartz.properties配置文件;
    如果找不到则读取quartz jar包中默认的配置文件quartz.properties;

     

  2. 从SchedulerRepository中根据名称读取已经创建的scheduler,
  3. 如果没有则重新创建一个,并保存在SchedulerRepository中。
  • 类中的常量:配置文件中的配置项key
  • 构造方法:
//一个很重要的属性:
    private PropertiesParser cfg;

//指定配置文件(以Properties对象形式配置,或者指定quartz.properties文件路径,或使用无参构造方法)   
    public StdSchedulerFactory() {
    }
    public StdSchedulerFactory(Properties props) throws SchedulerException {
        initialize(props);//调用初始化方法,直接初始化cfg属性
    }
    public StdSchedulerFactory(String fileName) throws SchedulerException {
        initialize(fileName);//调用初始化方法先加载解析配置文件
    }

 

  • 初始化方法:
initialize();initialize(InputStream);initialize(String);分别代表从(当前线程工作空间,从resource资源路径下,从Classpath类路径下加载quartz.propertites配置文件;从命令行启动参数指定的路径下)读取配置;从打开的File文件流中读取配置;从指定路径下读取配置;最后都是将配置文件解析为Properties对象,调用initialize(Properties)初始cfg属性;
  • 工厂方法(作为Schedule的工厂,最重要是提供Schedule实例:实例工厂接口)
    public static Scheduler getDefaultScheduler() throws SchedulerException {
        StdSchedulerFactory fact = new StdSchedulerFactory();

        return fact.getScheduler();
    }
//SchedulerRepository中持有一个静态HashMap,缓存所有的Schedule对象确保唯一性并且防止被GC回收,并且允许全局的查找-所有这些ClassLoader空间
    public Scheduler getScheduler(String schedName) throws SchedulerException {
//根据Schedule名称,从缓存中查找Schedule
        return SchedulerRepository.getInstance().lookup(schedName);
    }
//获取全部已缓存的Schedule实例
    public Collection<Scheduler> getAllSchedulers() throws SchedulerException {
        return SchedulerRepository.getInstance().lookupAll();
    }
public Scheduler getScheduler() throws SchedulerException {
        if (cfg == null) {
            //调用无参的初始化方法,加载配置文件,初始化cfg
            initialize();
        }

        // Schedule注册器,持有所有Schedule实例
        SchedulerRepository schedRep = SchedulerRepository.getInstance();
        //先查看已注册的Schedule
        Scheduler sched = schedRep.lookup(getSchedulerName());

        //如果Schedule已注册且已关闭,移除schedule,
        if (sched != null) {
            if (sched.isShutdown()) {
                schedRep.remove(getSchedulerName());
            } else {
                return sched;
            }
        }
        /*核心方法:instantiate主要是实例化相关资源对象,最最主要的工作:
            JobStore js = null;
            ThreadPool tp = null;
            QuartzScheduler qs = null;
            DBConnectionManager dbMgr = null;
            第一 properties注入全部采用的是 set方法反射注入 ,
            第二 全部初始化都是 initialize方法
            最后暴露Schedule实例
        * */
        sched = instantiate();
        return sched;
    }

核心方法instantiate()部分代码:对象的实例化和初始化基本类似,以JobStore为例:

基本步骤:1.从配置中获取org.quartz.jobStore.class 对应的值,即为指定的JobStore实现类全路径

                   2.反射实例化

                  3.从配置中获取org.quartz.jobStore前缀的配置项,即为JobStore的属性,通过调用setBeanProps(Object obj, Properties props)方法,进行set方法反射注入

 private Scheduler instantiate() throws SchedulerException {
    ......

        // Get JobStore Properties
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        //从配置中读取给定的JobStore类名,若未指定则抛出异常
        String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS,
                RAMJobStore.class.getName());

        if (jsClass == null) {
            initException = new SchedulerException(
                    "JobStore class not specified. ");
            throw initException;
        }

        try {
            // 加载指定的oJbStore实现类class对象,并调用class对象的newInstance()通过反射方法实例化
            // 此时调用的是无参构造方法,属性均未初始化
            js = (JobStore) loadHelper.loadClass(jsClass).newInstance();
        } catch (Exception e) {
            initException = new SchedulerException("JobStore class '" + jsClass
                    + "' could not be instantiated.", e);
            throw initException;
        }

        SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);

        // 获取配置中前缀为org.quartz.jobStore的配置项,排除前缀为.lockHandler的
        tProps = cfg.getPropertyGroup(PROP_JOB_STORE_PREFIX, true, new String[] {PROP_JOB_STORE_LOCK_HANDLER_PREFIX});
        try {
            // 反射注入jobStore的属性
            setBeanProps(js, tProps);
        } catch (Exception e) {
            initException = new SchedulerException("JobStore class '" + jsClass
                    + "' props could not be configured.", e);
            throw initException;
        }

        if (js instanceof JobStoreSupport) {
            // 设置自定义锁句柄(信号量),从配置文件中获取 .lockHandler.class 对应的全路径类名
            // Install custom lock handler (Semaphore)
            String lockHandlerClass = cfg.getStringProperty(PROP_JOB_STORE_LOCK_HANDLER_CLASS);
            if (lockHandlerClass != null) {
                try {
                    // 反射实例化指定类(信号量)
                    Semaphore lockHandler = (Semaphore)loadHelper.loadClass(lockHandlerClass).newInstance();

                    // 获取前缀为.lockHandler 配置项
                    tProps = cfg.getPropertyGroup(PROP_JOB_STORE_LOCK_HANDLER_PREFIX, true);

                    // If this lock handler requires the table prefix, add it to its properties.
                    if (lockHandler instanceof TablePrefixAware) {
                        tProps.setProperty(
                                PROP_TABLE_PREFIX, ((JobStoreSupport)js).getTablePrefix());
                        tProps.setProperty(
                                PROP_SCHED_NAME, schedName);
                    }

                    try {
                        // set反射注入属性
                        setBeanProps(lockHandler, tProps);
                    } catch (Exception e) {
                        initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass
                                + "' props could not be configured.", e);
                        throw initException;
                    }

                    ((JobStoreSupport)js).setLockHandler(lockHandler);
                    getLog().info("Using custom data access locking (synchronization): " + lockHandlerClass);
                } catch (Exception e) {
                    initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass
                            + "' could not be instantiated.", e);
                    throw initException;
                }
            }
        }
    ......
}
       

(二) JobStore和Schedule存储器

1.Schedule的实例全部存储在单例的ScheduleRepository中;

2.JobStore存储器,保存Job s 和 Trigger s,给QuartzSchedule使用,同时Job and Trigger的key应该是独一无二的.

支持三种存储配置:RAM(内存存储——直接JVM进程的);terracotta (Terracotta是一款由美国Terracotta公司开发的著名开源Java集群平台。);JDBC (MySQL 之类的 ....)

这个接口中定义的都是JobDetail和Trigger的增删改查的一些公共方法(在子类中实现各不相同,在内存中增删改查,在数据库中)

RAMJobStore类中,使用HashMap缓存任务和触发器,一个基于内存的存储, 很简单一堆集合包住就行了 . 主要注意的是 他会将Job 包装一下称为 JobWrapper


    protected HashMap<JobKey, JobWrapper> jobsByKey = new HashMap<JobKey, JobWrapper>(1000);

    protected HashMap<TriggerKey, TriggerWrapper> triggersByKey = new HashMap<TriggerKey, TriggerWrapper>(1000);

    protected HashMap<String, HashMap<JobKey, JobWrapper>> jobsByGroup = new HashMap<String, HashMap<JobKey, JobWrapper>>(25);

    protected HashMap<String, HashMap<TriggerKey, TriggerWrapper>> triggersByGroup = new HashMap<String, HashMap<TriggerKey, TriggerWrapper>>(25);

    protected TreeSet<TriggerWrapper> timeTriggers = new TreeSet<TriggerWrapper>(new TriggerWrapperComparator());

    protected HashMap<String, Calendar> calendarsByName = new HashMap<String, Calendar>(25);

    protected Map<JobKey, List<TriggerWrapper>> triggersByJob = new HashMap<JobKey, List<TriggerWrapper>>(1000);

    protected final Object lock = new Object();

    protected HashSet<String> pausedTriggerGroups = new HashSet<String>();

    protected HashSet<String> pausedJobGroups = new HashSet<String>();

    protected HashSet<JobKey> blockedJobs = new HashSet<JobKey>();
    
    protected long misfireThreshold = 5000l;

    protected SchedulerSignaler signaler;

    private final Logger log = LoggerFactory.getLogger(getClass());

(三)ThreadPool

 要为org.quartz.core.QuartzScheduler提供线程池的类,需要实现此接口。
 理想情况下,TheadPool实例应该只用于Quartz。
 最重要的是,当方法blockForAvailableThreads()获取可用线程的数量,返回1或者更大值时: 在稍后调用runInThread(Runnable)方法时,池中必须至少有一个可用线程。
 如果这个假设不成立,可能会导致额外的JobStore查询和更新,如果使用了集群特性,可能会导致更大的负载不平衡。

(四)QuartzSchedulerResources

resources 资源管理器, 包含job和线程池,整个Quartz 的核心资源, 所有资源都在这里.

(五)QuartzScheduler

Quartz的心脏,包含调用job的方法, 注册listener
// 继承链:QuartzScheduler->RemotableQuartzScheduler->Remote//构造器
/* 
* @see QuartzSchedulerResources
* @param resources 核心资源 QuartzSchedulerResources 从构造器中给QuartzScheduler
* @param idleWaitTime:当调度器发现当前没有可出发的触发器时,等待多少时间后再次校验,默认是30s,是个死循环, 如果等待时间过短, 会空转很快
* @param dbRetryInterval 废弃了
*/
public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)

(六)DBConnectionManager

管理 ConnectionProviders , 提供透明的连接访问 .

此处是桥接模式的实现:JobStore -> DBConnectionManager -> ConnectionProviders有一个管理者管理连接的提供者, 然后JobStore 可以通过他获取连接.

(七)JobListener 和 TriggerListener

所有的监听器都由ListenerManager进行管理;
任务监听器——当JobDetail运行的时候会通知你,通常Schedule的应用是不会使用这个监听器的。
执行流程
 triggerFired ->
​       vetoJobExecution ? false -> jobToBeExecuted - > 调用Job接口中的方法 - > jobWasExecuted
​                        ? true  -> jobExecutionVetoed
    //Job调用前
    void jobToBeExecuted(JobExecutionContext context);
    // veto 否决的意思 , 由 org.quartz.TriggerListener#vetoJobExecution 决定
    void jobExecutionVetoed(JobExecutionContext context);
    //Job调用后
    void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);

使用方法:实现这两个方法,然后配置就行:eg

org.quartz.jobListener.j1.class=com.example.springquartz.MyJobListener
// 比如属性设置可以这么做, 只要实现set方法就可以
// org.quartz.jobListener.l1.name=MyJobListener

org.quartz.triggerListener.t1.class=com.example.springquartz.MyTriggerListener

(八)Scheduler

1. Quartz Scheduler的主接口,对外暴露唯一接口,可以操作所有资源对象,一般使用StdSchedule。
里面包含了 QuartzScheduler , 所以可以操作其他对象.实现类:2. Scheduler的生命周期开始于其被创建时,结束于shutdown()方法调用。一旦对象创建完成,就可以用来操作Jobs和Triggers,包括添加、删除、查询等。但只有在Scheduler start()被调用后,才会按照Trigger定义的触发规则执行Job的内容。3. Scheduler的核心功能就是操作Job、Trigger、Calendar、Listener等。包括addXXX、deleteXXX、pauseXXX、resumeXXX等。

(九)核心运作流程 .

QuartzScheduler的构造方法中传入了QuartzSchedulerResources,将所有资源都放进去了。org.quartz.core.QuartzScheduler#QuartzScheduler 构造方法是执行的任务的核心 , 就是心脏 . 然后交给了 -> QuartzSchedulerThread 轮询处理;

QuartzSchedulerThread是一个线程类,负责查询并触发Triggers。

public class QuartzSchedulerThread extends Thread {
    QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs, boolean setDaemon, int threadPrio) {
        super(qs.getSchedulerThreadGroup(), qsRsrcs.getThreadName());
        ........
        paused = true;
        halted = new AtomicBoolean(false);
    }
}

QuartzSchedulerThread#run()方法的主要工作:

  • 等待QuartzScheduler启动
  • 查询待触发的Trigger
  • 等待Trigger触发时间到来
  • 触发Trigger
  • 循环上述步骤

Quartz暴露出来给用户使用的主要API

  • Job 接口,定义需要执行的任务
    //仅包含一个执行方法,Scheduler控制在每次Trigger触发时创建Job实例
    /**
    JobExecutionContext对象包含了当前任务执行的上下文环境,包括JobDetail、Trigger以及jobDataMap等。
    此方法的实现可能希望在此方法退出之前在{@link JobExecutionContext}上设置一个{@link JobExecutionContext#setResult(Object) result}对象。
    结果本身对于Quartz是没有意义的,但是对于监视作业执行的{@link JobListener} 或{@link TriggerListener} 可能是有用的。
    */
    public interface Job {
        void execute(JobExecutionContext context)throws JobExecutionException;
    }

     

  • JobDetail 包含job的基本信息,JobDetail用于保存Job相关的属性信息
    JobKey唯一确定了一个Job
    JobDataMap用于存储Job执行时必要的业务信息
    JobDetail保存的仅仅是Job接口的Class对象,而非具体的实例。
    JobBuilder负责JobDetail实例的创建,并且JobBuilder提供了链式操作,可以方便的为JobDetail添加额外的信息。

     

  • Trigger 描述Job执行的时间触发规则
    TriggerKey(group,name)唯一标识了Scheduler中的Trigger
    JobKey指向了该Trigger作用的Job
    一个Trigger仅能对应一个Job,而一个Job可以对应多个Trigger
    同样的,Trigger也拥有一个JobDataMap
    Priority:当多个trigger拥有相同的触发时间,根据该属性来确定先后顺序,数值越大越靠前,默认5,可为正负数
    Misfire Instructions:没来得及执行的机制。同一时间trigger数量过多超过可获得的线程资源,导致部分trigger无法执行。不同类型的Trigger拥有不同的机制。当Scheduler启动时候,首先找到没来得及执行的trigger,再根据不同类型trigger各自的处理策略处理
    Calendar:Quartz Calendar类型而不是java.util.Calendar类型。用于排除Trigger日程表中的特定时间范围,比如原本每天执行的任务,排除非工作

     

1.常见的两种Trigger为SimpleTrigger和CronTrigger.

Cron表达式由7部分组成,分别是秒 分 时 日期 月份 星期 年(可选),空格间隔。

2.Trigger的几种状态及其转化

  1. STATE_WAITING(默认): 等待触发

  2. STATE_ACQUIRED:获取

  3. STATE_COMPLETE:完成

  4. STATE_PAUSED:暂停

  5. STATE_BLOCKED:阻塞

  6. STATE_PAUSED_BLOCKED:从阻塞中暂停

  7. STATE_ERROR

  8. 在这里插入图片描述

参考:https://juejin.im/post/6844904053919449096#heading-2

https://www.jianshu.com/p/f2e78c1d8678

https://blog.csdn.net/GAMEloft9/article/details/89454747

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值