兄弟们,今天来盘一盘Quartz定时任务框架里的一个超重要注解——@DisallowConcurrentExecution!这玩意儿能帮你精准控制任务的并发执行,解决很多定时任务中的大坑!

一、为啥需要@DisallowConcurrentExecution?先看个“翻车现场”

上周组里新来的实习生写了个定时任务,给用户发送营销短信:

1
2
3
4
5
6
7
8
9
10
11
12
13
@DisallowConcurrentExecution // 实习生手抖注释掉了这行
public class MarketingJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 发送百万级营销短信
System.out.println("发送营销短信中...");
try {
Thread.sleep(60000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

结果呢?任务执行时间超过了触发间隔,Quartz直接启动了多个任务实例,导致短信重复发送,用户投诉爆炸!这就是典型的任务并发执行问题

二、@DisallowConcurrentExecution是干啥的?

这个注解的作用一句话概括:禁止同一任务的多个实例同时执行

核心原理:

  • 当任务类加上@DisallowConcurrentExecution注解后,Quartz会保证:
    • 同一个任务(JobDetail)的多个实例不会同时执行
    • 即使任务执行时间超过了触发间隔,也不会启动新的实例
    • 下一次执行会等到上一次执行完成后才开始

对比实验:

情况 无@DisallowConcurrentExecution 有@DisallowConcurrentExecution
任务执行时间>触发间隔 启动多个实例(并发执行) 等待上一次完成(串行执行)
任务状态 多个实例同时运行 始终只有一个实例在运行

三、如何使用@DisallowConcurrentExecution?

1. 直接加在Job类上

1
2
3
4
5
6
7
@DisallowConcurrentExecution
public class MarketingJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 任务逻辑
}
}

2. 配合CronTrigger使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(MarketingJob.class)
.withIdentity("marketingJob", "group1")
.build();

// 创建CronTrigger,每分钟执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("marketingTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ?"))
.build();

// 注册到scheduler
scheduler.scheduleJob(jobDetail, trigger);

3. 注意事项

  • 只对同一任务有效:不同的Job类不受影响
  • 基于JobKey判断:Quartz通过JobKey(名称+组)来识别任务
  • 持久化存储:如果使用JDBC存储Job数据,需要确保JobDetail的durability为true

四、@DisallowConcurrentExecution vs @PersistJobDataAfterExecution

这俩注解经常一起出现,但作用完全不同:

注解 作用
@DisallowConcurrentExecution 禁止同一任务的多个实例同时执行,解决并发问题
@PersistJobDataAfterExecution 任务执行后保存JobDataMap的修改,解决数据持久化问题

组合使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class DataSyncJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();

// 获取上次执行的状态
int lastCount = dataMap.getInt("count");

// 更新状态
dataMap.put("count", lastCount + 1);

// 同步数据...
}
}

五、实战场景:哪些情况需要用它?

1. 任务执行时间不确定

比如批量数据处理、文件上传下载等耗时操作,避免多个实例同时运行导致资源耗尽。

2. 操作共享资源

例如操作同一个文件、修改同一个数据库记录,防止并发冲突。

3. 幂等性无法保证的任务

有些操作不是幂等的(多次执行结果不同),必须保证串行执行。

六、常见问题及解决办法

1. 任务还是并发执行了?

  • 检查注解是否生效:确认Job类上是否正确添加了注解
  • 检查JobDetail配置:确保所有实例使用相同的JobKey
  • 检查触发器配置:确认没有为同一JobDetail配置多个触发器

2. 任务阻塞导致后续任务无法执行?

可以考虑:

  • 优化任务逻辑:减少单次执行时间
  • 使用@PersistJobDataAfterExecution:分批次执行大型任务
  • 调整线程池配置:为Quartz分配更多线程

七、总结:记住这个使用口诀

  • 耗时操作:加上@DisallowConcurrentExecution,避免并发
  • 数据持久化:配合@PersistJobDataAfterExecution,保存状态
  • 关键资源:必须串行执行,确保数据安全

兄弟们,掌握了这个注解,Quartz定时任务的并发问题就再也难不倒你了!赶紧检查一下你的项目,看看哪些任务需要加上这个“并发控制符”~