Quartz源码节选(四)启动Scheduler

参考资料

第一篇配合第二篇阅读,讲解一些基本概念。若已了解可跳过。
推荐阅读第三篇,因为本文是基于第三篇的笔记。

DEMO

一个DEMO,每3秒输出helloworld

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getMergedJobDataMap();
        String say = jobDataMap.getString("say");
        System.out.println(say);
    }
}
public class Demo {
    public static void main(String[] args) {
        JobDetail jd = JobBuilder.newJob(MyJob.class)
                .withIdentity("myJob","jobGroup1")
                .withDescription("a demo jobDetail")
                .usingJobData("say","helloworld")
                .build();

        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity("myCronTrigger","triggerGroup1")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * * * ?"))
                .forJob(jd)
                .build();
        
        try {
            Scheduler scheduler = new StdSchedulerFactory().getScheduler(); 
            scheduler.scheduleJob(jd,cronTrigger);
            scheduler.start();//本文要讲的部分
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

启动scheduler

  • QuartzScheduler.start()
public void start() throws SchedulerException {

    if (shuttingDown|| closed) {
        throw new SchedulerException(
            "The Scheduler cannot be restarted after shutdown() has been called.");
    }

    // QTZ-212 : calling new schedulerStarting() method on the listeners
    // right after entering start()
    notifySchedulerListenersStarting();

    if (initialStart == null) {
        initialStart = new Date();
        this.resources.getJobStore().schedulerStarted();//RAMJobStore什么都不做,JobStoreSupport会判断是否集群并尝试恢复Job
        startPlugins();
    } else {
        resources.getJobStore().schedulerResumed();
    }

    schedThread.togglePause(false); //核心代码

    getLog().info(
        "Scheduler " + resources.getUniqueIdentifier() + " started.");

    notifySchedulerListenersStarted();
}

  • QuartzSchedulerThread.togglePause(boolean pause)
void togglePause(boolean pause) {
    synchronized (sigLock) {
        paused = pause;

        if (paused) {
            signalSchedulingChange(0);
        } else {
            sigLock.notifyAll();//唤醒sigLock的等待线程
        }
    }
}

这里有两个疑问,QuartzSchedulerThread是什么时候创建并启动的?谁在等待sigLock?

QuartzSchedulerThread的创建和启动

StdSchedulerFacotry.instantiate()会创建一个QuartzScheduler实例,QuartzSchedulerThread就是在QuartzScheduler的构造方法中创建并启动的。

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
    throws SchedulerException {
    this.resources = resources;
    if (resources.getJobStore() instanceof JobListener) {
        addInternalJobListener((JobListener)resources.getJobStore());
    }

    this.schedThread = new QuartzSchedulerThread(this, resources);//创建QuartzSchedulerThread
    ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
    schedThreadExecutor.execute(this.schedThread);//启动QuartzSchedulerThread
    if (idleWaitTime > 0) {
        this.schedThread.setIdleWaitTime(idleWaitTime);
    }

    jobMgr = new ExecutingJobsManager();
    addInternalJobListener(jobMgr);
    errLogger = new ErrorLogger();
    addInternalSchedulerListener(errLogger);

    signaler = new SchedulerSignalerImpl(this, this.schedThread);
    
    getLog().info("Quartz Scheduler v." + getVersion() + " created.");
}

通过sigLock控制QuartzSchedulerThread.run()

sigLock是QuartzSchedulerThread的一个final成员,且sigLock.wait()集中在QuartzSchedulerThread.run()中,可见sigLock是用来控制QuartzSchedulerThread的运行。

QuartzSchedulerThread.run()找出可用的Trigger和JobDetail

当ThreadPool有空闲线程时

找一批即将触发的Trigger

等待一段时间,直到最紧急的Trigger触发的前一刻

让Trigger触发,并找到对应的JobDetail

创建JobRunShell,JobDetail放入其中

JobRunShell带着JobDetail在ThreadPool中运行

public void run() {
    int acquiresFailed = 0;

    while (!halted.get()) {
        try {
            // check if we're supposed to pause...
            synchronized (sigLock) {
                while (paused && !halted.get()) {
                    try {
                        // wait until togglePause(false) is called...
                        // 等待 togglePause(false)将其唤醒
                        sigLock.wait(1000L);
                    } catch (InterruptedException ignore) {
                    }

                    // reset failure counter when paused, so that we don't
                    // wait again after unpausing
                    acquiresFailed = 0;
                }

                if (halted.get()) {
                    break;
                }
            }

            // wait a bit, if reading from job store is consistently
            // failing (e.g. DB is down or restarting)..
            if (acquiresFailed > 1) {
                try {
                    long delay = computeDelayForRepeatedErrors(qsRsrcs.getJobStore(), acquiresFailed);
                    Thread.sleep(delay);
                } catch (Exception ignore) {
                }
            }
			//阻塞,直到ThreadPool有空闲的线程
            int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
            if(availThreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads...

                List<OperableTrigger> triggers;

                long now = System.currentTimeMillis();

                clearSignaledSchedulingChange();
                try {
                    //得到一批即将触发的Trigger
                    triggers = qsRsrcs.getJobStore().acquireNextTriggers(
                            now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
                    acquiresFailed = 0;
                    if (log.isDebugEnabled())
                        log.debug("batch acquisition of " + (triggers == null ? 0 : triggers.size()) + " triggers");
                } catch (JobPersistenceException jpe) {
                    if (acquiresFailed == 0) {
                        qs.notifySchedulerListenersError(
                            "An error occurred while scanning for the next triggers to fire.",
                            jpe);
                    }
                    if (acquiresFailed < Integer.MAX_VALUE)
                        acquiresFailed++;
                    continue;
                } catch (RuntimeException e) {
                    if (acquiresFailed == 0) {
                        getLog().error("quartzSchedulerThreadLoop: RuntimeException "
                                +e.getMessage(), e);
                    }
                    if (acquiresFailed < Integer.MAX_VALUE)
                        acquiresFailed++;
                    continue;
                }

                if (triggers != null && !triggers.isEmpty()) {

                    now = System.currentTimeMillis();
                    long triggerTime = triggers.get(0).getNextFireTime().getTime();
                    long timeUntilTrigger = triggerTime - now;
                    while(timeUntilTrigger > 2) {
                        synchronized (sigLock) {
                            if (halted.get()) {
                                break;
                            }
                            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);//Trigger还没到触发时间,先等会
                                } catch (InterruptedException ignore) {
                                }
                            }
                        }
                        if(releaseIfScheduleChangedSignificantly(triggers, triggerTime)) {
                            break;
                        }
                        now = System.currentTimeMillis();
                        timeUntilTrigger = triggerTime - now;
                    }

                    // this happens if releaseIfScheduleChangedSignificantly decided to release triggers
                    if(triggers.isEmpty())
                        continue;

                    // set triggers to 'executing'
                    List<TriggerFiredResult> bndles = new ArrayList<TriggerFiredResult>();

                    boolean goAhead = true;
                    synchronized(sigLock) {
                        goAhead = !halted.get();
                    }
                    if(goAhead) {
                        try {
                            List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
                            if(res != null)
                                bndles = res;
                        } catch (SchedulerException se) {
                            qs.notifySchedulerListenersError(
                                    "An error occurred while firing triggers '"
                                            + triggers + "'", se);
                            //QTZ-179 : a problem occurred interacting with the triggers from the db
                            //we release them and loop again
                            for (int i = 0; i < triggers.size(); i++) {
                                qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                            }
                            continue;
                        }

                    }

                    for (int i = 0; i < bndles.size(); i++) {
                        TriggerFiredResult result =  bndles.get(i);
                        TriggerFiredBundle bndle =  result.getTriggerFiredBundle();//bndle里面有JobDetail
                        Exception exception = result.getException();

                        if (exception instanceof RuntimeException) {
                            getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
                            qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                            continue;
                        }

                        // it's possible to get 'null' if the triggers 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) {
                            qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                            continue;
                        }

                        JobRunShell shell = null;
                        try {
                            shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
                            shell.initialize(qs);
                        } catch (SchedulerException se) {
                            qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                            continue;
                        }

                        if (qsRsrcs.getThreadPool().runInThread(shell) == false) {//让shell在ThreadPool中运行
                            // 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(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                        }

                    }

                    continue; // while (!halted)
                }
            } else { // if(availThreadCount > 0)
                // should never happen, if threadPool.blockForAvailableThreads() follows contract
                continue; // while (!halted)
            }

            long now = System.currentTimeMillis();
            long waitTime = now + getRandomizedIdleWaitTime();
            long timeUntilContinue = waitTime - now;
            synchronized(sigLock) {
                try {
                  if(!halted.get()) {
                    // QTZ-336 A job might have been completed in the mean time and we might have
                    // missed the scheduled changed signal by not waiting for the notify() yet
                    // Check that before waiting for too long in case this very job needs to be
                    // scheduled very soon
                    if (!isScheduleChanged()) {
                      sigLock.wait(timeUntilContinue);
                    }
                  }
                } catch (InterruptedException ignore) {
                }
            }

        } catch(RuntimeException re) {
            getLog().error("Runtime error occurred in main trigger firing loop.", re);
        }
    } // while (!halted)

    // drop references to scheduler stuff to aid garbage collection...
    qs = null;
    qsRsrcs = null;
}

ThreadPool

ThreadPool,Job运行的地方,主要关注这几个接口方法

public interface ThreadPool {
    void initialize() throws SchedulerConfigException;// 初始化ThreadPool
    int blockForAvailableThreads();// 阻塞,直到有空闲线程,然后返回空闲线程的数量
    boolean runInThread(Runnable runnable); //在ThreadPool中运行一个任务
    /*
     *其他接口
     */
}

以SimpleThreadPool为例

public class SimpleThreadPool implements ThreadPool {
    //三个List维护WorkerThread的信息
    private List<WorkerThread> workers;
    private LinkedList<WorkerThread> availWorkers = new LinkedList<WorkerThread>();
    private LinkedList<WorkerThread> busyWorkers = new LinkedList<WorkerThread>();
    /*
     *其他成员
     */
}

WorkerThread在ThreadPool初始化的时候就已经启动。如果你还记得的话,ThreadPool.initialize()被StdSchedulerFactory.instantiate()调用。

  • SimpleThreadPool.initialize()
public void initialize() throws SchedulerConfigException {

    if(workers != null && workers.size() > 0) // already initialized...
        return;
    
    if (count <= 0) {
        throw new SchedulerConfigException(
                "Thread count must be > 0");
    }
    if (prio <= 0 || prio > 9) {
        throw new SchedulerConfigException(
                "Thread priority must be > 0 and <= 9");
    }

    if(isThreadsInheritGroupOfInitializingThread()) {
        threadGroup = Thread.currentThread().getThreadGroup();
    } else {
        // follow the threadGroup tree to the root thread group.
        threadGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup parent = threadGroup;
        while ( !parent.getName().equals("main") ) {
            threadGroup = parent;
            parent = threadGroup.getParent();
        }
        threadGroup = new ThreadGroup(parent, schedulerInstanceName + "-SimpleThreadPool");
        if (isMakeThreadsDaemons()) {
            threadGroup.setDaemon(true);
        }
    }


    if (isThreadsInheritContextClassLoaderOfInitializingThread()) {
        getLog().info(
                "Job execution threads will use class loader of thread: "
                        + Thread.currentThread().getName());
    }

    // create the worker threads and start them
    Iterator<WorkerThread> workerThreads = createWorkerThreads(count).iterator();
    while(workerThreads.hasNext()) {
        WorkerThread wt = workerThreads.next();
        wt.start(); // 启动工作线程
        availWorkers.add(wt);
    }
}
  • SimpleThreadPool.runInThread(Runnable runnable)
public boolean runInThread(Runnable runnable) {
    if (runnable == null) {
        return false;
    }

    synchronized (nextRunnableLock) {

        handoffPending = true;

        // Wait until a worker thread is available
        while ((availWorkers.size() < 1) && !isShutdown) {
            try {
                nextRunnableLock.wait(500);
            } catch (InterruptedException ignore) {
            }
        }

        if (!isShutdown) {
            WorkerThread wt = (WorkerThread)availWorkers.removeFirst();
            busyWorkers.add(wt);
            wt.run(runnable); //运行JobRunShell
        } else {
            // If the thread pool is going down, execute the Runnable
            // within a new additional worker thread (no thread from the pool).
            WorkerThread wt = new WorkerThread(this, threadGroup,
                    "WorkerThread-LastJob", prio, isMakeThreadsDaemons(), runnable);
            busyWorkers.add(wt);
            workers.add(wt);
            wt.start();
        }
        nextRunnableLock.notifyAll();
        handoffPending = false;
    }

    return true;
}

JobRunShell调用JobDetail方法

关键代码在job.execute(jec);处

  • JobRunShell.run()
public void run() {
    qs.addInternalSchedulerListener(this);

    try {
        OperableTrigger trigger = (OperableTrigger) jec.getTrigger();
        JobDetail jobDetail = jec.getJobDetail();

        do {

            JobExecutionException jobExEx = null;
            Job job = jec.getJobInstance();

            try {
                begin();
            } catch (SchedulerException se) {
                qs.notifySchedulerListenersError("Error executing Job ("
                        + jec.getJobDetail().getKey()
                        + ": couldn't begin execution.", se);
                break;
            }

            // notify job & trigger listeners...
            try {
                if (!notifyListenersBeginning(jec)) {
                    break;
                }
            } catch(VetoedException ve) {
                try {
                    CompletedExecutionInstruction instCode = trigger.executionComplete(jec, null);
                    qs.notifyJobStoreJobVetoed(trigger, jobDetail, instCode);
                    
                    // QTZ-205
                    // Even if trigger got vetoed, we still needs to check to see if it's the trigger's finalized run or not.
                    if (jec.getTrigger().getNextFireTime() == null) {
                        qs.notifySchedulerListenersFinalized(jec.getTrigger());
                    }

                    complete(true);
                } catch (SchedulerException se) {
                    qs.notifySchedulerListenersError("Error during veto of Job ("
                            + jec.getJobDetail().getKey()
                            + ": couldn't finalize execution.", se);
                }
                break;
            }

            long startTime = System.currentTimeMillis();
            long endTime = startTime;

            // execute the job
            try {
                log.debug("Calling execute on job " + jobDetail.getKey());
                job.execute(jec);//关键代码,直接直接调用Job.execute
                endTime = System.currentTimeMillis();
            } catch (JobExecutionException jee) {
                endTime = System.currentTimeMillis();
                jobExEx = jee;
                getLog().info("Job " + jobDetail.getKey() +
                        " threw a JobExecutionException: ", jobExEx);
            } catch (Throwable e) {
                endTime = System.currentTimeMillis();
                getLog().error("Job " + jobDetail.getKey() +
                        " threw an unhandled Exception: ", e);
                SchedulerException se = new SchedulerException(
                        "Job threw an unhandled exception.", e);
                qs.notifySchedulerListenersError("Job ("
                        + jec.getJobDetail().getKey()
                        + " threw an exception.", se);
                jobExEx = new JobExecutionException(se, false);
            }

            jec.setJobRunTime(endTime - startTime);

            // notify all job listeners
            if (!notifyJobListenersComplete(jec, jobExEx)) {
                break;
            }

            CompletedExecutionInstruction instCode = CompletedExecutionInstruction.NOOP;

            // update the trigger
            try {
                instCode = trigger.executionComplete(jec, jobExEx);
            } catch (Exception e) {
                // If this happens, there's a bug in the trigger...
                SchedulerException se = new SchedulerException(
                        "Trigger threw an unhandled exception.", e);
                qs.notifySchedulerListenersError(
                        "Please report this error to the Quartz developers.",
                        se);
            }

            // notify all trigger listeners
            if (!notifyTriggerListenersComplete(jec, instCode)) {
                break;
            }

            // update job/trigger or re-execute job
            if (instCode == CompletedExecutionInstruction.RE_EXECUTE_JOB) {
                jec.incrementRefireCount();
                try {
                    complete(false);
                } catch (SchedulerException se) {
                    qs.notifySchedulerListenersError("Error executing Job ("
                            + jec.getJobDetail().getKey()
                            + ": couldn't finalize execution.", se);
                }
                continue;
            }

            try {
                complete(true);
            } catch (SchedulerException se) {
                qs.notifySchedulerListenersError("Error executing Job ("
                        + jec.getJobDetail().getKey()
                        + ": couldn't finalize execution.", se);
                continue;
            }

            qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode);
            break;
        } while (true);

    } finally {
        qs.removeInternalSchedulerListener(this);
    }
}
发布了10 篇原创文章 · 获赞 1 · 访问量 6826

猜你喜欢

转载自blog.csdn.net/jakekong/article/details/104901842