一、常见的定时器
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());
}
整个启动过程简化流程:
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()。
任务执行时序图:
因为前面已经置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()具体是如何实现的。
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,释放锁;