Quartz Job是如何执行的

在前一篇Quartz是如何到期触发定时任务的我们通过对源码的分析,了解的Quartz的触发机制。接下来的这一篇,我们分析Job是如何执行的。

for (int i = 0; i < bndles.size(); i++) {
    TriggerFiredResult result =  bndles.get(i);
    TriggerFiredBundle bndle =  result.getTriggerFiredBundle();
    ......
    JobRunShell shell = null;
    try {
        shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
        shell.initialize(qs);
    } catch (SchedulerException se) {
       ......
    }

    if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
       ......
    }
}

对已经触发的触发器列表,我们通过其创建一个待执行的JobRunShell线程,然后通过线程池来执行其中的任务。
要知道,我们的定时任务应该是长这个样子的,例如:

/**
 * job示例
 * Created by gameloft9 on 2019/4/8.
 */
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
@Data
public class PrintJob implements InterruptableJob {

    @Autowired
    EchoService echoService;

    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            String countStr = context.getJobDetail().getJobDataMap().getString("count");
            long count = 0;
            if (countStr != null){
                count = Long.parseLong(countStr);
            }

            context.getJobDetail().getJobDataMap().put("count", "" + (count + 1));

            // 模拟任务执行
            echoService.echo("执行任务中,jobDesc: " + context.getJobDetail().getDescription());

            log.info("任务执行成功,累计执行次数:{}",count);
        } catch (Exception e) {
            log.error("", e);
        } finally {
        }
    }

    public void interrupt() throws UnableToInterruptJobException {
        // do nothing
    }

那么执行任务时,是如何获取到这个Job的实例的呢?JobRunShell和我们的Job又有什么联系呢?答案就在这个JobRunShell里。
在创建JobShell后,会对其进行初始化:

 public void initialize(QuartzScheduler sched)
        throws SchedulerException {
        this.qs = sched;

        Job job = null;
        JobDetail jobDetail = firedTriggerBundle.getJobDetail();

        try {
            job = sched.getJobFactory().newJob(firedTriggerBundle, scheduler);
        } catch (SchedulerException se) {
           .......
        } catch (Throwable ncdfe) { // such as NoClassDefFoundError
          .......
        }

        this.jec = new JobExecutionContextImpl(scheduler, firedTriggerBundle, job);
    }

初始化做了两件事情,一个是通过工厂类创建我们的Job对象实例,另一个就是创建一个Job执行的上下文。在spring集成Quartz中,这个JobFactory就是AdaptableJobFactory。创建Job的代码很简单,直接根据反射创建类实例,如下所示:

@Override
	public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
		try {
			Object jobObject = createJobInstance(bundle);
			return adaptJob(jobObject);
		}
		catch (Exception ex) {
			throw new SchedulerException("Job instantiation failed", ex);
		}
	}
	protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
		return bundle.getJobDetail().getJobClass().newInstance();
	}

插一句题外话,如果我们想要在Job里注入一些spring的bean,我们就可以在这个AdaptableJobFactory上面做文章,例如:Job自动注入Spring Bean

到目前为止,我们Job对象有了,而JobRunShell其实是对Job的一层封装。剩下的问题就是Job里的execut方法是如何执行的?要解决这个问题,我们需要先去了解Quartz的线程池模型。

在quartz.properties配置文件中,我们的线程池对象是SimpleThreadPool,如下:

#============================================================================
# Configure ThreadPool  
#============================================================================
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5

SimpleThreadPool的初始化方法如下:

public void initialize() throws SchedulerConfigException {
    ......
    // 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);
    }
}

这里创建了count个工作线程,然后启动,最后加入到总线程列表和可用线程列表里。工作线程(WorkerThread)的定义如下:

 class WorkerThread extends Thread {

        private final Object lock = new Object(); // 锁,用于加入任务

        private AtomicBoolean run = new AtomicBoolean(true); // 标记线程是否要停止

        private SimpleThreadPool tp; // 线程池

        private Runnable runnable = null; // 待跑的任务
        
        private boolean runOnce = false;
        .......
        }

我们再回过头来,看看runInthread做了什么:

 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);
            } else {
              ......
            }
            nextRunnableLock.notifyAll();
            handoffPending = false;
        }

        return true;
    }

线程列表是一个临界资源,所以需要通过nextRunnable来上锁。每来一个要跑的线程时,先判断可用线程列表是否有空闲线程,如果没有则wait一小会,直到有空闲线程出来。接下来,先将该线程从可用线程列表里去除,并加入到业务线程列表里。然后再run方法里替换线程(不是线程执行!),并通知WorkerThread有线程加入。注意这个run方法并不是实现Runnable接口的run方法。

public void run(Runnable newRunnable) {
            synchronized(lock) {
                if(runnable != null) {
                    throw new IllegalStateException("Already running a Runnable!");
                }

                runnable = newRunnable;
                lock.notifyAll();
            }
        }

WorkerThread实现Runnable接口的run方法如下:

@Override
public void run() {
    boolean ran = false;
    
    while (run.get()) {
        try {
            synchronized(lock) {
                while (runnable == null && run.get()) {
                    lock.wait(500);
                }

                if (runnable != null) {
                    ran = true;
                    runnable.run();
                }
            }
        } catch (InterruptedException unblock) {
            // do nothing (loop will terminate if shutdown() was called
            try {
                getLog().error("Worker thread was interrupt()'ed.", unblock);
            } catch(Exception e) {
                // ignore to help with a tomcat glitch
            }
        } catch (Throwable exceptionInRunnable) {
            try {
                getLog().error("Error while executing the Runnable: ",
                    exceptionInRunnable);
            } catch(Exception e) {
                // ignore to help with a tomcat glitch
            }
        } finally {
            synchronized(lock) {
                runnable = null;
            }
            // repair the thread in case the runnable mucked it up...
            if(getPriority() != tp.getThreadPriority()) {
                setPriority(tp.getThreadPriority());
            }

            if (runOnce) {
                   run.set(false);
                clearFromBusyWorkersList(this);
            } else if(ran) {
                ran = false;
                makeAvailable(this);
            }

        }
    }

    //if (log.isDebugEnabled())
    try {
        getLog().debug("WorkerThread is shut down.");
    } catch(Exception e) {
        // ignore to help with a tomcat glitch
    }
}

首先线程会一直等待,直到前面通过run(Runnable)方法加进来了任务。等任务加进来后,运行任务。注意虽然JobShell也是一个线程,但这里是直接调用JobShell的run方法,而不是start它。任务完成后,将runnable置为null,并将该线程从业务线程列表里去掉,并加入可用线程列表。重新开启下一次循环等待。
现在,我们再来看JobShell的run方法里,到底做了什么

 public void run() {
        try {
            do {             
                Job job = jec.getJobInstance();
                ......
                // execute the job
                try {
                   
                    job.execute(jec);
                    endTime = System.currentTimeMillis();
                } catch (JobExecutionException jee) {
                  .......
                }
                
                // update the trigger
                try {
                    instCode = trigger.executionComplete(jec, jobExEx);
                } catch (Exception e) {
                   ......
                }

                // 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;
                }

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

        } finally {
            qs.removeInternalSchedulerListener(this);
        }
    }

为了突出重点,这里省略了很多代码。我们可以看到一行关键性的代码

job.execute(jec);

就是这里调用了我们Job的execute方法,去执行我们真正的业务代码。然后会触发一系列注册的监听事件。另外,如果在任务执行过程中发生异常,而且我们的异常指定了要重新执行,那么会立即重新执行任务过程。

综上,Quartz任务的执行,如果搞懂了线程池的实现,并能理解观察者模式,基本上也就都懂了,然后就剩一些细节了。

你可能感兴趣的:(quartz,定时任务框架Quartz系列)