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

Spring Boot多线程场景下代码块非线程安全及空值异常排查求助

代码非线程安全原因分析及高性能解决方案

核心问题场景

Spring Boot应用通过调度任务从数据库拉取数据存入ConcurrentHashMap(tempMap),多线程基于key查询时,在tempMap未刷新的情况下出现返回空Optional,调用.get()抛出No value present异常,同时存在线程安全隐患。

非线程安全的具体原因

1. getSingle方法的错误Optional用法

tempMap.get(value)可能返回null(key不存在或刷新竞态导致),但代码中使用Optional.of(...)而非Optional.ofNullable(...)——虽会直接抛出NullPointerException,但如果刷新时存在可见性问题,线程读取到旧的空map,也会触发后续.get()的异常。更关键的是,该方法未正确处理key不存在的场景。

2. getMultiple方法的竞态条件

getMultiple中先执行tempMap.containsKey(value)再调用tempMap.get(s),这两个操作并非原子:

  • 线程A执行containsKey(value)返回true
  • 此时调度任务刷新tempMap,移除了该key
  • 线程A再执行get(s)返回null,map操作将其转为Optional.empty()
  • 最终调用.get()抛出No value present异常

3. tempMap刷新的可见性与原子性缺失

即使ConcurrentHashMap本身线程安全,但如果刷新操作存在以下问题:

  • 直接在原有map上执行clear()+putAll(),这两个操作非原子,中间线程会看到空或半填充的map
  • tempMap变量未用volatile修饰,刷新时替换map引用后,其他线程可能无法立刻看到最新的map,仍读取旧的失效数据

高性能修复方案

1. 修复查询方法的竞态与Optional用法

  • 修正getSingle:用ofNullable正确处理null值:
private Optional<DummyClassEntity> getSingle(String value) {
    return Optional.ofNullable(tempMap.get(value));
}
  • 重构getMultiple:直接通过get获取值并过滤null,消除containsKey与get的竞态:
private Optional<DummyClassEntity> getMultiple(String[] values) {
    return Arrays.stream(values)
        .map(tempMap::get)
        .filter(Objects::nonNull)
        .findFirst();
}

2. 保证刷新操作的原子性与可见性

推荐原子替换map引用的方式,性能最优且无锁:

// 用volatile保证引用的可见性
private volatile ConcurrentHashMap<String, DummyClassEntity> tempMap = new ConcurrentHashMap<>();

// 调度任务中的刷新逻辑
public void refreshTempMap() {
    // 先在新map中加载完整数据
    ConcurrentHashMap<String, DummyClassEntity> newMap = new ConcurrentHashMap<>();
    loadDataFromDatabase(newMap);
    // 原子替换引用,所有线程立刻看到完整的新map
    tempMap = newMap;
}

这种方式下,线程要么读取旧的完整map,要么读取新的完整map,不会看到中间的无效状态,无需加锁,性能不受影响。

3. 安全处理Optional空值

调用方避免直接使用.get(),改用更安全的方式处理空场景:

if (sender.length == 1) {
    return getSingle(sender[0])
        .orElseThrow(() -> new IllegalArgumentException("未找到key: " + sender[0]));
} else {
    return getMultiple(sender)
        .orElseThrow(() -> new IllegalArgumentException("传入的key均无匹配数据"));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 15:20:30