为什么Quartz同时师弟 我出去执行任务两个任务会出问题

博客分类:
JDK自带的Timer和TimerTask类,但是想要执行每天 1:00 定时执行,并且需要一个稳定的框架,就需要找Quartz,至于Quartz是什么,我想百度会告诉大家的。二话不说,下载了 Quartz包,下载的是官方最新版2.1.7。然后兴致勃勃的百度起来,关于Quartz的用法,文章还是挺多的,但是,Copy进来后,总是报错。无 奈,最新版的用法,网上又很难找到。怎么办呢?自己E文又不好!哎,E文是我永远的痛啊,谁叫俺爱国呢。翻了一下下载的Quartz包,发现里面有好多例 子,然后挨个挨个跑例子。哈,功夫不负有心人啊,第三个例子刚好能解决我现在的问题,再仔细推敲一下,发现Quartz原来如此好用。为了方便日后重复利 用,也为了其他没用过Quartz而且E文也不怎么好的朋友不再受折磨,就决定把这次的使用心德记录下来。
  第一步:引包
  要使用Quartz,必须要引入以下这几个包:
  1、log4j-1.2.16
  2、quartz-2.1.7
  3、slf4j-api-1.6.1.jar
  4、slf4j-log4j12-1.6.1.jar
  这些包都在下载的Quartz包里面包含着,因此没有必要为寻找这几个包而头疼。
  第二步:创建要被定执行的任务类
  这一步也很简单,只需要创建一个实现了org.quartz.Job接口的类,并实现这个接口的唯一一个方法execute(JobExecutionContext arg0) throws JobExecutionException即可。如:
import java.text.SimpleDateF
import java.util.D
import org.quartz.J
import org.quartz.JobExecutionC
import org.quartz.JobExecutionE
public class myJob implements Job {
public void execute(JobExecutionContext arg0) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(sdf.format(new Date()));
import java.text.SimpleDateF
import java.util.D
import org.quartz.J
import org.quartz.JobExecutionC
import org.quartz.JobExecutionE
public class myJob implements Job {
public void execute(JobExecutionContext arg0) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(sdf.format(new Date()));
  这个例子很简单,就不用解说了。
  第三步:创建任务调度,并执行
  这一步应该算是最难的一步的,但其实是非常简单的,直接上代码
import static org.quartz.CronScheduleBuilder.cronS
import static org.quartz.JobBuilder.newJ
import static org.quartz.TriggerBuilder.newT
import java.text.SimpleDateF
import java.util.D
import org.quartz.CronT
import org.quartz.JobD
import org.quartz.S
import org.quartz.SchedulerF
import org.quartz.impl.StdSchedulerF
public class Test {
public void go() throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
JobDetail job = newJob(myJob.class).withIdentity("job1", "group1").build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?")).build();
Date ft = sched.scheduleJob(job, trigger);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(job.getKey() + " 已被安排执行于: " + sdf.format(ft) + ",并且以如下重复规则重复执行: " + trigger.getCronExpression());
job = newJob(myJob.class).withIdentity("job2", "group1").build();
trigger = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule("15 0/2 * * * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " 已被安排执行于: " + sdf.format(ft) + ",并且以如下重复规则重复执行: "+ trigger.getCronExpression());
sched.start();
Thread.sleep(60L * 1000L);
} catch (Exception e) {}
sched.shutdown(true);
public static void main(String[] args) throws Exception {
Test test = new Test();
test.go();
import static org.quartz.CronScheduleBuilder.cronS
import static org.quartz.JobBuilder.newJ
import static org.quartz.TriggerBuilder.newT
import java.text.SimpleDateF
import java.util.D
import org.quartz.CronT
import org.quartz.JobD
import org.quartz.S
import org.quartz.SchedulerF
import org.quartz.impl.StdSchedulerF
public class Test {
public void go() throws Exception {
// 首先,必需要取得一个Scheduler的引用
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
//jobs可以在scheduled的sched.start()方法前被调用
//job 1将每隔20秒执行一次
JobDetail job = newJob(myJob.class).withIdentity("job1", "group1").build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?")).build();
Date ft = sched.scheduleJob(job, trigger);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(job.getKey() + " 已被安排执行于: " + sdf.format(ft) + ",并且以如下重复规则重复执行: " + trigger.getCronExpression());
// job 2将每2分钟执行一次(在该分钟的第15秒)
job = newJob(myJob.class).withIdentity("job2", "group1").build();
trigger = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule("15 0/2 * * * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " 已被安排执行于: " + sdf.format(ft) + ",并且以如下重复规则重复执行: "+ trigger.getCronExpression());
// 开始执行,start()方法被调用后,计时器就开始工作,计时调度中允许放入N个Job
sched.start();
//主线程等待一分钟
Thread.sleep(60L * 1000L);
} catch (Exception e) {}
//关闭定时调度,定时器不再工作
sched.shutdown(true);
public static void main(String[] args) throws Exception {
Test test = new Test();
test.go();
  OK了,Job1和Job2就会被安排为定时执行了。此时程序是可以执行的了,但是可能会输出WARN级别日志,这是因为没有加log4j的配置文件,加上配置文件,就OK了。这里需要说明的地方只有一个,其它的可以直接Copy到您的项目里面。看代码:
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?")).build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?")).build();
  "0/20 * * * * ?"代表什么?这是关键,搞明白这个,Quartz就能帮助你解决大部分的定时执行任务的功能。详细解释请看下面转载见容
以上内容属“世纪魔面”原创,以下内容转载自
CronTrigger配置格式:格式: [秒] [分] [小时] [日] [月] [周] [年]
允许填写的值
允许的通配符
, - * ? / L W
1-12 or JAN-DEC
1-7 or SUN-SAT
, - * ? / L #
通配符说明: * 表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。 ? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ? - 表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。 , 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发 / 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。 L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本 月最后一个星期五" W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").
'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发(一般指发工资 )
# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了)
0 0 12 * * ?
每天12点触发
0 15 10 ? * *
每天10点15分触发
0 15 10 * * ?
每天10点15分触发
0 15 10 * * ? *
每天10点15分触发
0 15 10 * * ? 2005
2005年每天10点15分触发
0 * 14 * * ?
每天下午的 2点到2点59分每分触发
0 0/5 14 * * ?
每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ?
每天下午的 2点到2点59分(整点开始,每隔5分触发) 每天下午的 18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ?
每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED
3月分每周三下午的 2点10分和2点44分触发
0 15 10 ? * MON-FRI
从周一到周五每天上午的10点15分触发
0 15 10 15 * ?
每月15号上午10点15分触发
0 15 10 L * ?
每月最后一天的10点15分触发
0 15 10 ? * 6L
每月最后一周的星期五的10点15分触发
0 15 10 ? * 6L
从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3
每月的第三周的星期五开始触发
0 0 12 1/5 * ?
每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ?
每年的11月11号 11点11分触发(光棍节)
浏览: 6517 次
来自: 北京
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'<strong class="up-num- 人点赞
之前已经把功能基本都实现了,这里我们再来优化一下代码。
我们发现,在创建、修改、和删除定时任务时,对于quartz的操作其实是可以封装成一个简单的工具辅助类的,如创建的代码可以抽取成:
* 创建定时任务
* @param scheduler the scheduler
* @param jobName the job name
* @param jobGroup the job group
* @param cronExpression the cron expression
* @param isSync the is sync
* @param param the param
public static void createScheduleJob(Scheduler scheduler, String jobName, String jobGroup,
String cronExpression, boolean isSync, Object param) {
//同步或异步
Class&? extends Job& jobClass = isSync ? JobSyncFactory.class : JobFactory.
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
//放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleJobVo.JOB_PARAM_KEY, param);
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup)
.withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
LOG.error(&创建定时任务失败&, e);
throw new ScheduleException(&创建定时任务失败&);
把任务的具体信息包括Scheduler都使用参数方式传入。
看过前面文章的同学或许还记得,quartz在spring中需要声明的对象只剩下一行:
&bean id=&schedulerFactoryBean& class=&org.springframework.scheduling.quartz.SchedulerFactoryBean& /&
既然这个schedulerFactoryBean只是在spring中声明一下,并没有做特殊的操作,在辅助的工具类中直接使用单例模式创建一个不是更好,还能少传一个参数?
你想的没错,这种方式的确更好还能解耦,但是我们来看一下SchedulerFactoryBean类的代码:
public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean&Scheduler&, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
它实现了spring的FactoryBean接口,也就是说它在创建时并不是简单的new而已,还夹杂了一些其它的复杂行为,所以我们也没必要特地的去怎么怎么样,还是在spring中声明一下吧。
另外,我们看它的getObject()方法:
public Scheduler getObject() {
return this.
发现它实际返回的已经是Scheduler对象,既然如此在我们类中就不必注入schedulerFactoryBean再调用getScheduler()这么麻烦了,可以直接声明Scheduler对象:
public class ScheduleJobServiceImpl implements ScheduleJobService {
/** 调度Bean */
@Autowired
当然你注入schedulerFactoryBean也不会有错,看过spring源码的同学应该立马就能明白这是getBean(&bean&)和getBean(&&bean&)的区别了。
另外说说前面漏掉的两个地方。
一、更新任务
先前我们在更新任务时,虽然更新了定时任务的执行时间,但是并没有对参数进行更新,即使用context.getMergedJobDataMap().get(...)方法获取到的参数还是旧的。
假设我们更新了任务的时间表达式,任务已按新的时间表达式在执行,但在获取到参数后发现时间表达式还是原来的。
尝试对参数进行更新,使用如下代码:
JobDetail jobDetail = scheduler.getJobDetail(getJobKey(jobName, jobGroup));
//jobDetail = jobDetail.getJobBuilder().ofType(jobClass).build();
//更新参数 实际测试中发现无法更新
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put(ScheduleJobVo.JOB_PARAM_KEY, param);
jobDetail.getJobBuilder().usingJobData(jobDataMap);
发现无法更新,试过其它几个api发现都不行,没有办法,最后采用了先删除任务再进行创建的方式来迂回实现参数的更新。demo中更新任务有直接修改方式和删除修改方式,区别就在这里。
二、任务的同步和异步
同步和异步在quartz 2.2的版本中对于使用者来说区别只在于是否在job类上添加了@DisallowConcurrentExecution注解。
按时这个特点我们建立两个job的实现工厂类,在其中一个类上添加注解@DisallowConcurrentExecution,然后可以根据添加任务时的参数来确定具体使用哪个:
//同步或异步
Class&? extends Job& jobClass = isSync ? JobSyncFactory.class : JobFactory.
需要注意在定时任务运行时更新是没有办法改变同步和异步的。
以下是最新整理的demo图:
接下来说说我在整合使用时碰到的一些已知问题。
一、更新任务时参数问题。也就是前面说的无法更新任务中传入的参数。
二、同步或异步在定时任务运行时修改是不能改变的,这个在前面也提到了。
三、在定时任务运行时修改,可能会该让任务长时间处于线程阻塞状态,即BLOCKED状态,即使你的任务中只有简单的一行System.out输出。要使它恢复也很简单,删除重建即可。
四、定时任务运行两次的问题。这个也是网上传的最多的问题,这里来着重的说一下。
网上流传引起该问题的原因目前主要有两个说法:
1 spring配置文件加载了多次,导致quartz的bean被实例化多次而导致任务多次执行。
2 tomcat的webapps目录问题。tomcat运行时加载了两次配置文件导致任务多次执行。
这两个说法在我的demo中应该并不存在,但为了验证我也尝试了下。
不使用tomcat,在main方法中用编程的方式启动spring,甚至不使用spring,直接用quartz官方给出的代码:
// Grab the Scheduler instance from the Factory
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
scheduler.start();
scheduler.shutdown();
} catch (SchedulerException se) {
se.printStackTrace();
问题还是存在,这就说明不是配置文件加载的问题了,这应该是quartz本身存在的一个bug,而且这个多次运行是很有规律的,基本按如下套路走:
定为5秒运行一次,一切正常,没有多次执行现象发生。
定为10秒运行一次,一切正常,没有多次执行现象发生。
定为29秒运行一次,运行时一次正常,一次不正常。
定为59秒运行一次,运行时一次正常,一次不正常。
以上是我实测得出的,再长时间就没测了,毕竟太耗时。在有运行两次的现象时都是间隔的,即一次正常一次不正常这种方式。
既然推断是quartz本身存在bug,那我们又要如何解决这个问题了?
其实在我个人看来,这个问题是无关紧要的,为什么说无关紧要呢?这就涉及到你项目业务设计的是否完善,代码是否健壮了。
一个设计良好的业务方法,特别是那些供外部调用的接口或方法,应该都支持幂等性,何为幂等性?即这个方法同样的参数至少在一个时间区间内,我调用1次和调用10次100次,结果都是一样的。
支持了幂等性,前面说的运行两次的情况是不是就无关紧要了?在有些定时任务为分布式设计的系统(后面会探讨)中,为了确保定时任务的执行甚至会故意人为的去调用两次。
当然支持幂等性最好是在进入方法时就判断,发现已经执行过时就立即返回而不是真的再去同样的结果再执行一遍,以节省资源。
完整Demo源码地址:
本部分内容设定了隐藏,需要回复后才能浏览(游客回复无效)
<span class="up-num-
本文标题:Spring 整合Quartz 2实现定时任务四:细化调整及一些已知的问题
本文地址:
本文来自,转载请注明出处
交流QQ群:博客分类:
使用Quartz做计划任务时,默认情况下,当前任务总会执行,无论前一个任务是否结束。从如下日志可以看出,job被并发执行,无视前面任务是否结束。
13:14:04,095 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:14:04,095 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - n=1
13:14:04,095 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - group1.job1 executing at Sun Apr 07 13:14:04 CST 2013
13:14:06,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:14:06,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - n=2
13:14:06,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - group1.job1 executing at Sun Apr 07 13:14:06 CST 2013
13:14:07,095 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - group1.job1 done.
13:14:08,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:14:08,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-3 - n=3
13:14:08,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-3 - group1.job1 executing at Sun Apr 07 13:14:08 CST 2013
13:14:09,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - group1.job1 done.
因此,如果采用默认条件,需要考虑并发执行的逻辑问题,否则需要设置为顺序执行。
第一种办法是为自定义的job加上@org.quartz.DisallowConcurrentExecution注解,告诉quartz不允许并发执行job任务。这个办法粗粒度地解决了并发问题,缺少灵活度,但很简单,也很简洁。
13:20:04,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:20:04,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - n=1
13:20:04,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - group1.job1 executing at Sun Apr 07 13:20:04 CST 2013
13:20:07,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - group1.job1 done.
13:20:07,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:20:07,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - n=2
13:20:07,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - group1.job1 executing at Sun Apr 07 13:20:07 CST 2013
13:20:10,017 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - group1.job1 done.
第二种办法是在job的执行方法体内编写处理代码
13:24:18,236 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:24:18,236 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - n=1
13:24:18,236 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - group1.job1 executing at Sun Apr 07 13:24:18 CST 2013
13:24:20,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:24:20,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-2 - &
13:24:21,236 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-1 - group1.job1 done.
13:24:22,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_QuartzSchedulerThread - new a simple job
13:24:22,001 INFO
[creative.air.schedule.quartz.SimpleJob] DefaultQuartzScheduler_Worker-3 - n=2
相关测试代码:
最后需要注意的是,quartz的发展出现了分支,目前存在并行版本
spring与quartz1集成更友好
浏览: 328356 次
来自: 北京
讲的很清楚,明白
中文乱码式硬伤
启动失败的原因是加载的类版本冲突,因此你首先要保证依赖的版本和 ...
请问我按照你的方式修改还是报错
错误信息还是这个 是为什么啊 ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'Quartz寤鸿?鎬绘槸鏀句竴涓?嚜宸辩殑quartz.properties鏂囦欢鍦╟lasspath涓嬶紝杩愯?鏃跺彲瑕嗙洊quartz.jar涓?殑quartz.properties鐨勯厤缃?紝鍥犱负鏍规嵁鑷?繁瀹為檯鐨勫簲鐢?紝闇}

我要回帖

更多关于 执行者任务机 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信