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

Spring Boot抽象控制器@Scheduled重复执行问题求助

解决Spring Boot抽象控制器定时任务重复执行的问题

嘿,这个坑我之前踩过!你猜的没错——每个继承ElCorDeLaCiutatController的控制器实例都会被Spring初始化,而每个实例都会注册自己的@Scheduled定时任务,所以才会出现每秒执行十几次的情况。下面给你几个靠谱的解决方案,按推荐程度排序:

1. 把定时任务抽离到独立的单例服务类(最推荐)

控制器的核心职责是处理HTTP请求,把定时任务放在控制器里本身就违反了单一职责原则。咱们把价格更新的调度逻辑单独放到一个@Service类里,Spring默认会把这个类创建为单例,所以定时任务只会执行一次。

代码示例:

首先创建调度服务类:

@Service
public class PriceUpdateScheduler {
    // 用volatile保证多线程下的可见性,或者用AtomicDouble更安全
    public static volatile Double price = 0.0;
    private final PriceService priceService;

    // 构造注入比@Autowired更推荐哦
    public PriceUpdateScheduler(PriceService priceService) {
        this.priceService = priceService;
    }

    @Scheduled(fixedRate = 1000)
    private void updatePrice() throws DataAccessException, SQLException, ClientProtocolException, IOException {
        price = getCurrentPrice();
    }

    private Double getCurrentPrice() {
        // 这里写调用priceService获取价格的逻辑
        return priceService.getLatestPrice();
    }
}

然后修改你的抽象控制器,注入这个调度服务即可使用price变量:

public abstract class ElCorDeLaCiutatController {
    @Autowired
    private PriceUpdateScheduler priceUpdateScheduler;

    // 业务逻辑里直接用PriceUpdateScheduler.price就行
    protected Double getSharedPrice() {
        return PriceUpdateScheduler.price;
    }
}

2. 用静态标记控制定时任务只启动一次(应急方案)

如果因为某些原因必须把定时任务留在抽象控制器里,可以用一个静态的原子布尔值来标记任务是否已经启动,在控制器初始化的时候判断,只启动一次任务。不过这种方式不够优雅,不推荐长期使用。

代码示例:

public abstract class ElCorDeLaCiutatController {
    public static volatile Double price = 0.0;
    private static final AtomicBoolean taskStarted = new AtomicBoolean(false);
    @Autowired
    PriceService priceService;

    @PostConstruct
    private void initScheduler() {
        // 只有当任务还没启动时,才注册定时任务
        if (taskStarted.compareAndSet(false, true)) {
            // 这里需要手动注册定时任务,因为@Scheduled会被每个实例触发,所以不能用注解了
            ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
            scheduler.scheduleAtFixedRate(() -> {
                try {
                    updatePrice();
                } catch (Exception e) {
                    // 记得处理异常
                    e.printStackTrace();
                }
            }, 0, 1, TimeUnit.SECONDS);
        }
    }

    private void updatePrice() throws DataAccessException, SQLException, ClientProtocolException, IOException {
        price = getPrice();
    }

    protected abstract Double getPrice();
}

3. 利用Spring Scheduled的唯一ID(版本依赖)

从Spring Framework 4.3.12开始,@Scheduled支持uniqueId属性,你可以给定时任务指定一个全局唯一的ID,这样即使多个实例尝试注册同一个任务,Spring也只会执行一次。

代码示例:

public abstract class ElCorDeLaCiutatController {
    public static Double price = 0.0;
    @Autowired
    PriceService priceService;

    // 指定唯一ID,确保任务只执行一次
    @Scheduled(fixedRate = 1000, uniqueId = "global-price-update-task")
    private void updatePrice() throws DataAccessException, SQLException, ClientProtocolException, IOException {
        price = getPrice();
    }
}

注意:这个特性需要你的Spring版本符合要求,而且依赖Spring的调度器配置,不如第一种方案稳定。

额外提醒:线程安全问题

因为price是静态变量,多个线程可能同时读取和修改它,一定要保证线程安全。推荐用AtomicDouble代替普通的Double:

public static final AtomicDouble price = new AtomicDouble(0.0);

// 更新时
price.set(getCurrentPrice());

// 获取时
double currentPrice = price.get();

内容的提问来源于stack exchange,提问作者en Peris

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:10:44