You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 05:34:55