如何避免Quartz调度器与REST调用触发的configUpdate方法并行执行?
确保configUpdate方法不并发执行的实现方案
根据你的场景(Quartz定时触发+REST API触发,异步执行且需避免并发),以下是几种实用的实现方案:
一、单机场景:Java内置可重入锁(ReentrantLock)
这是单机部署下最简单直接的方案,通过ReentrantLock的tryLock方法快速判断是否已有执行中的实例,避免阻塞直接跳过重复触发:
import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ConfigService { private static final Logger log = LoggerFactory.getLogger(ConfigService.class); private final ReentrantLock updateLock = new ReentrantLock(); public void configUpdate() { // 尝试获取锁,无阻塞,获取失败直接返回 if (!updateLock.tryLock()) { log.info("configUpdate方法正在执行,本次触发已跳过"); return; } try { // 执行你的复杂异步逻辑 executeComplexConfigUpdate(); } finally { // 确保锁最终释放,避免死锁 updateLock.unlock(); } } private void executeComplexConfigUpdate() { // 实际业务逻辑 } }
注意:如果你的configUpdate方法本身是通过@Async注解实现异步的,要确保锁的范围覆盖整个异步任务的执行周期,避免锁提前释放。
二、统一调度入口:Quartz Job并发控制
将REST API触发的请求也转发到Quartz Job执行,利用Quartz自带的并发控制注解@DisallowConcurrentExecution,从调度层面避免同一Job的并发执行:
1. 定义Quartz Job并禁用并发
import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @DisallowConcurrentExecution public class ConfigUpdateJob implements Job { private final ConfigService configService; // 构造注入ConfigService public ConfigUpdateJob(ConfigService configService) { this.configService = configService; } @Override public void execute(JobExecutionContext context) throws JobExecutionException { configService.executeComplexConfigUpdate(); } }
2. REST控制器触发Quartz Job
import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ConfigController { private final Scheduler scheduler; public ConfigController(Scheduler scheduler) { this.scheduler = scheduler; } @GetMapping("/trigger-config-update") public ResponseEntity<String> triggerConfigUpdate() { try { JobDetail job = JobBuilder.newJob(ConfigUpdateJob.class).build(); Trigger trigger = TriggerBuilder.newTrigger().startNow().build(); scheduler.scheduleJob(job, trigger); return ResponseEntity.ok("配置更新任务已触发"); } catch (Exception e) { return ResponseEntity.internalServerError().body("触发失败:" + e.getMessage()); } } }
优势:统一了定时和手动触发的入口,Quartz会自动保证同一Job不会并发执行,无需额外锁逻辑。
三、集群场景:分布式锁
如果你的应用是多实例集群部署,单机锁和Quartz注解无法跨实例生效,此时需要用分布式锁,以Redis的Redisson实现为例:
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; public class ConfigService { private static final Logger log = LoggerFactory.getLogger(ConfigService.class); private final RedissonClient redissonClient; private static final String LOCK_KEY = "config-update-lock"; public ConfigService(RedissonClient redissonClient) { this.redissonClient = redissonClient; } public void configUpdate() { RLock lock = redissonClient.getLock(LOCK_KEY); try { // 尝试获取锁,等待5秒,锁持有时间30分钟(可根据业务调整) if (!lock.tryLock(5, 1800, TimeUnit.SECONDS)) { log.info("configUpdate方法正在执行,本次触发已跳过"); return; } executeComplexConfigUpdate(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("获取分布式锁时被中断", e); } finally { // 确保只有当前线程持有锁时才释放 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } private void executeComplexConfigUpdate() { // 实际业务逻辑 } }
替代方案:如果没有Redis,也可以用ZooKeeper实现分布式锁,逻辑类似。
四、无中间件场景:数据库锁
如果无法引入Redis/ZooKeeper等中间件,可以利用数据库的原子更新操作实现分布式锁:
1. 先创建锁表
CREATE TABLE config_lock ( lock_key VARCHAR(64) PRIMARY KEY, locked TINYINT(1) DEFAULT 0, lock_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 初始化锁记录 INSERT INTO config_lock (lock_key, locked) VALUES ('config_update', 0);
2. 代码实现锁逻辑
import org.springframework.jdbc.core.JdbcTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ConfigService { private static final Logger log = LoggerFactory.getLogger(ConfigService.class); private final JdbcTemplate jdbcTemplate; private static final String LOCK_KEY = "config_update"; public ConfigService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void configUpdate() { // 原子更新获取锁 int updateCount = jdbcTemplate.update( "UPDATE config_lock SET locked = 1, lock_time = NOW() WHERE lock_key = ? AND locked = 0", LOCK_KEY ); if (updateCount == 0) { log.info("configUpdate方法正在执行,本次触发已跳过"); return; } try { executeComplexConfigUpdate(); } finally { // 释放锁 jdbcTemplate.update( "UPDATE config_lock SET locked = 0 WHERE lock_key = ?", LOCK_KEY ); } } private void executeComplexConfigUpdate() { // 实际业务逻辑 } }
补充:可以添加锁超时自动释放逻辑,比如定时任务清理超过N分钟的锁,避免因服务异常导致锁无法释放。
内容的提问来源于stack exchange,提问作者ultimateraman
相关产品推荐
相关产品推荐

