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

初始化后无修改的HashMap并发读出现元素丢失及锁重复获取问题

问题根因
  • **即使没有后续写操作,普通HashMap也不保证并发场景下的读安全,你的问题核心来自两方面:
  1. 安全发布缺失:你当前的lockMap没有使用final修饰,也没有做任何安全发布处理。根据Java内存模型的规定,构造函数内对非final字段的赋值操作,不保证对其他线程立即可见。也就是说其他线程可能看到一个半初始化的HashMap:比如内部哈希表数组还未完成赋值,或者部分entry数据没有同步到主存,导致并发get操作时要么找不到已存在的key,要么返回null,甚至拿到错乱的entry对象。
  2. 代码逻辑漏洞:你在containsKey判断key不存在后仅打印日志,但没有终止流程,后续依然执行lockMap.get(ii).tryLock()会直接触发NPE。同时你两次调用get分别获取锁、执行解锁,也存在两次读取状态不一致的风险。
  • 你观测到的两次hello连续打印的现象,就是因为不同线程读取到HashMap的错乱状态,拿到了不同的ReentrantLock实例,因此同时加锁成功进入临界区;后续找不到key、NPE的异常,就是半初始化状态的HashMap读取返回null导致的。
修复方案
    1. 优先替换为ConcurrentHashMap:Java 8及以上版本的ConcurrentHashMap读操作全程无锁,仅写操作时才会加细粒度锁,你这种初始化后完全无写操作的场景下,性能和普通HashMap几乎没有差异,你担心的性能开销完全不存在。
    1. 若不使用ConcurrentHashMap,必须做安全发布:将lockMap声明为final类型,构造函数初始化完成后用Collections.unmodifiableMap()包裹生成不可变视图,保证内部状态对所有线程可见。
    1. 修复代码逻辑漏洞:将多次查询优化为单次get存入局部变量,key不存在时直接终止流程,同时将解锁操作放到finally块中避免锁泄漏:
void runConcurrently() {
    int ii = 10;
    ReentrantLock lock = lockMap.get(ii);
    if (lock == null) {
        log.error("lock id is not found in the lockMap " + ii);
        return;
    }
    boolean locked = lock.tryLock();
    if (!locked) {
        return;
    }
    try {
        runCriticialSection();
    } finally {
        lock.unlock();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 17:48:04