WMX · 随记
·Java· 3 分钟阅读

ConcurrentHashMap 计数:别把 get 加 put 当原子操作

使用了并发 Map,却仍然出现计数丢失,因为“读取旧值再写入新值”是两个独立操作。容器安全与业务更新原子性并不等价。

Java 系列概览:类型与值、状态与资源、边界验证
Java 系列概览:类型与值、状态与资源、边界验证 点击图片查看大图 ↗

问题从哪里出现

使用了并发 Map,却仍然出现计数丢失,因为“读取旧值再写入新值”是两个独立操作。容器安全与业务更新原子性并不等价。

理解核心机制

merge 和 compute 系列方法可以按键进行原子更新。对简单频次统计,merge(key, 1, Integer::sum) 表达清楚;高竞争统计还可以评估 LongAdder。

动手做一个小实验

Java 17 或更新版本;将示例保存为 Demo.java,执行 javac Demo.java 后运行 java Demo。

Java10 行 · 可横向滚动
import java.util.concurrent.ConcurrentHashMap;
public class Demo {
    public static void main(String[] args) {
        var counts = new ConcurrentHashMap<String, Integer>();
        for (String word : new String[]{"a", "b", "a"}) {
            counts.merge(word, 1, Integer::sum);
        }
        System.out.println(counts.get("a"));
    }
}

适用边界与常见误区

计算函数不要包含长时间阻塞或复杂的嵌套修改。一个键的原子更新不等于多个键之间的事务,也不保证遍历时得到全局瞬时快照。

继续练习

设计两个线程同时更新同一键的测试,比较 get/put 与 merge;单次运行正确不能证明不存在竞态。

参考资料

阅读相关官方文档或原始资料 ↗

← 返回文章归档