quartz笔记

210 阅读3分钟

一、常见的定时器

java.util.Time

二、quartz

Quartz 的核心类有以下三部分:

  • 任务 Job & JobDetail :  Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。。
  • 触发器 Trigger :  用于定义调度任务的时间规则,包括 simpleSchedule 和 CronTrigger
  • 调度器 Scheduler :  任务调度器(绑定Trigger触发器和Job)。

下面从这个demo入手,看看是如何执行的:

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("任务被执行了。。。");
    }
}
public static void main(String[] args) throws Exception {
    // 1.创建调度器 Scheduler
    SchedulerFactory factory = new StdSchedulerFactory();///***StdSchedulerFactory
    Scheduler scheduler = factory.getScheduler();//*****默认是实现类是 StdScheduler,实际是QuartzScheduler
    // 2.创建JobDetail实例,并与MyJob类绑定(Job执行内容)
    JobDetail job = JobBuilder.newJob(MyJob.class)
        .withIdentity("job1""group1")//
        .build();
    // 3.构建Trigger实例,每隔30s执行一次
    Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("trigger1""group1")
        .startNow()
        .withSchedule(simpleSchedule()
                      .withIntervalInSeconds(30)
                      .repeatForever())
        .build();
     ...
    // 4.执行,开启调度器
    scheduler.scheduleJob(job, trigger);/////****
    scheduler.start();///***
    //主线程睡眠1分钟,然后关闭调度器
    TimeUnit.MINUTES.sleep(1);
    scheduler.shutdown();
    System.out.println(System.currentTimeMillis());
}

整个启动过程简化流程:

image.png SchedulerFactoryBean实现了InitializingBean接口,初始化bean时会执行afterPropertiesSet(),创建StdScheduler。

Quartz的核心流程大致分为三个阶段:

1、获取调度实例阶段

    public void afterPropertiesSet() throws Exception {
        ...
        this.scheduler = this.prepareScheduler(this.prepareSchedulerFactory());///***
        try {
            this.registerListeners();
            this.registerJobsAndTriggers();
        } catch (Exception var4) {
            ...
        }
    }

内部调用路径:prepareScheduler()->createScheduler()->schedulerFactory.getScheduler()

getScheduler()中,根据配置文件加载配置和初始化,默认实现类是StdScheduler,实际是QuartzScheduler。在QuartzScheduler的构造函数中,创建一个QuartzSchedulerThread,是用来进行任务调度的线程。初始时线程paused=true,一直等待直到start方法将paused置为false。

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval) throws SchedulerException {
    ...
    this.schedThread = new QuartzSchedulerThread(this, resources);
    ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
    schedThreadExecutor.execute(this.schedThread);
    ...
    this.addInternalJobListener(this.jobMgr);
    this.addInternalSchedulerListener(this.errLogger);
}

2、绑定JobDetail和Trigger阶段

Scheduler将任务和触发器添加到JobStore中,如果是使用数据库存储信息,这时候会把任务持久化到Quartz核心表中,同时也会对实现JobListener的监听者通知任务已添加

public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
        this.resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
        this.notifySchedulerListenersJobAdded(jobDetail);
        this.notifySchedulerThread(trigger.getNextFireTime().getTime());
        this.notifySchedulerListenersSchduled(trigger);
}

内部调用路径:StdScheduler.schedulejob()->QuartzScheduler.schedulejob()。

3、启动调度器阶段

调用QuartzScheduler.start()。

public void start() throws SchedulerException {
    ...
    this.resources.getJobStore().schedulerStarted();
    ...
}
public void schedulerStarted() throws SchedulerException {
    ...
    this.clusterManagementThread = new JobStoreSupport.ClusterManager();//ClusterManager用来进行集群故障检测和处理
    ...
    this.misfireHandler = new JobStoreSupport.MisfireHandler();//用来进行misfire任务的处理
    this.schedThread.togglePause(false);//置paused=false,调度线程才真正开始调度。
}

路径:QuartzScheduler.start()->JobStoreSupport.schedulerStarted()。

任务执行时序图:

image.png

因为前面已经置paused=false,线程终于可以开始调度了,执行run()。

#QuartzSchedulerThread
public void run() {
    int availThreadCount = this.qsRsrcs.getThreadPool().blockForAvailableThreads();//去线程池中获取可用的线程
    ...
    //从JobStore获取(接下来30s内将要执行的)触发器,等待该触发器触发。
    triggers = this.qsRsrcs.getJobStore().acquireNextTriggers(now + this.idleWaitTime, Math.min(availThreadCount, this.qsRsrcs.getMaxBatchSize()), this.qsRsrcs.getBatchTimeWindow());///***
}
    if (triggers != null && !triggers.isEmpty()) {
        ...
        List<TriggerFiredResult> res = this.qsRsrcs.getJobStore().triggersFired(triggers);///***
        ...
        //创建一个JobRunShell(就是一个Runnable),然后从线程池中调用线程执行该任务。
        shell = this.qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
        shell.initialize(this.qs);
        ...
        ////调用线程池的runInThread方法,实际上是调用JobRunShell的run()方法
        if (!this.qsRsrcs.getThreadPool().runInThread(shell)) {}
        ...
    } else {
        this.sigLock.wait(timeUntilContinue);//triggers为空就等待
    }
    
    
#JobRunShell
public void run() {
     //获取trigger、JobDetail以及生成Job实例,然后执行job的execute接口函数
    OperableTrigger trigger = (OperableTrigger)this.jec.getTrigger();
    JobDetail jobDetail = this.jec.getJobDetail();
    Job job = this.jec.getJobInstance();
    job.execute(this.jec);
}

QuartzSchedulerThread会从SimpleThreadPool查看下有多少可用工作线程,然后找JobStore去拿下一批符合条件的待触发的Trigger任务列表,包装成FiredTriggerBundle。通过JobRunShellFactory创建FiredTriggerBundle的执行线程实例JobRunShell,然后把JobRunShell实例交给SimpleThreadPool的工作线程去执行。SimpleThreadPool会从可用线程队列拿出对应数量的线程,去调用JobRunShell的run()方法,此时会执行任务类的execute方法 : job.execute(JobExecutionContext context)。

接下来,看看QuartzSchedulerThread.run()-> acquireNextTriggers()和triggersFired()具体是如何实现的。

image.png

    public List<OperableTrigger> acquireNextTriggers(final long noLaterThan, final int maxCount, final long timeWindow) throws JobPersistenceException {
        String lockName;
        if (!this.isAcquireTriggersWithinLock() && maxCount <= 1) {
            lockName = null;//不加锁
        } else {
            lockName = "TRIGGER_ACCESS";//***加锁
        }

        return (List)this.executeInNonManagedTXLock(lockName, new JobStoreSupport.TransactionCallback<List<OperableTrigger>>() {
            public List<OperableTrigger> execute(Connection conn) throws JobPersistenceException {
                return JobStoreSupport.this.acquireNextTrigger(conn, noLaterThan, maxCount, timeWindow);
            }
        }, ...
    }

maxCount 来自 QuartzSchedulerThread,代表 Scheduler 一次拉取

trigger的最大数量,默认是 1。

triggers = qsRsrcs.getJobStore().acquireNextTriggers( now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());

triggersFired: 更新firedTrigger的status=EXECUTING; 更新trigger下一次触发的时间; 更新trigger的状态:无状态的trigger->WAITING,有状态的trigger->BLOCKED,若nextFireTime==null ->COMPLETE; commit connection,释放锁;