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
相关产品推荐
相关产品推荐

