ConcurrentHashMap 计数:别把 get 加 put 当原子操作
使用了并发 Map,却仍然出现计数丢失,因为“读取旧值再写入新值”是两个独立操作。容器安全与业务更新原子性并不等价。
问题从哪里出现
使用了并发 Map,却仍然出现计数丢失,因为“读取旧值再写入新值”是两个独立操作。容器安全与业务更新原子性并不等价。
理解核心机制
merge 和 compute 系列方法可以按键进行原子更新。对简单频次统计,merge(key, 1, Integer::sum) 表达清楚;高竞争统计还可以评估 LongAdder。
动手做一个小实验
Java 17 或更新版本;将示例保存为 Demo.java,执行 javac Demo.java 后运行 java Demo。
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;单次运行正确不能证明不存在竞态。