Java中访问定时调度更新的变量偶发返回空结果如何解决?
问题根因分析
你提到的「items列表在mapper.getAllItems()返回前被清空」的情况不会发生,Java中赋值操作的执行逻辑是先完成右值的计算/调用,拿到完整返回结果后才会赋值给左变量,因此不存在赋值中间状态的空列表。偶发返回空列表的核心原因是以下两点:
- 共享变量可见性问题:
items是多线程共享的静态变量,未添加任何同步修饰符,定时任务线程更新items的引用后,调用getItemById的业务线程可能无法立即感知到最新值,极端场景下会读到之前缓存的空列表。 - 无效值覆盖问题:如果某次定时任务执行时,数据库查询出现波动、超时等异常,导致
mapper.getAllItems()返回空列表或null,会直接覆盖items的原有有效值,导致后续请求拿到空结果。 - 启动初始值问题:服务刚启动时第一次定时任务还未执行完成,此时调用接口会读到类初始化时生成的空
ArrayList。
解决方案
方案1:轻量优化(推荐适配当前场景)
添加volatile修饰符保证多线程变量可见性,同时新增非空校验避免无效值覆盖,再添加启动时初始化逻辑避免初期空返回,代码修改如下:
public class A { // 添加volatile保证可见性,初始值用不可变空列表避免意外修改 private static volatile List<Item> items = Collections.emptyList(); // 服务启动后立即执行一次数据拉取,不用等定时任务触发 @PostConstruct public void init() { updateListItem(); } @Scheduled(fixedDelay = 3000) public void updateListItem() { List<Item> newItems = mapper.getAllItems(); // 只有查询返回非空有效数据才更新,避免异常空值覆盖原有数据 if (newItems != null && !newItems.isEmpty()) { items = newItems; } } public List<Item> getItemById(String id) { return items.stream().filter(item -> Objects.equals(id, item.id())) .collect(Collectors.toList()); } }
方案2:读写锁加固(适合后续有列表修改需求的场景)
如果后续业务需要对items列表做元素增删改操作,而非直接替换引用,可以用读写锁保证线程安全:
public class A { private static final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private static List<Item> items = Collections.emptyList(); @PostConstruct public void init() { updateListItem(); } @Scheduled(fixedDelay = 3000) public void updateListItem() { List<Item> newItems = mapper.getAllItems(); if (newItems == null || newItems.isEmpty()) { return; } rwLock.writeLock().lock(); try { items = newItems; } finally { rwLock.writeLock().unlock(); } } public List<Item> getItemById(String id) { rwLock.readLock().lock(); try { return items.stream().filter(item -> Objects.equals(id, item.id())) .collect(Collectors.toList()); } finally { rwLock.readLock().unlock(); } } }
内容的提问来源于stack exchange,提问作者Hanyu Yuzuru
相关产品推荐
相关产品推荐

