在前一篇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任务的执行,如果搞懂了线程池的实现,并能理解观察者模式,基本上也就都懂了,然后就剩一些细节了。