
本文深入剖析在 concurrenthashmap 中使用 mutableinteger 手动实现计数时线程不安全的根源,指出“看似加锁却仍出错”的本质在于非原子的 get-put-compute 操作序列,并提供基于 merge/compute 的标准、简洁、线程安全的替代方案。
你的代码看似做了多重防护:MutableInteger 的 getValue() 和 setValue() 方法用 synchronized 加锁,ConcurrentHashMap 本身也是线程安全的容器——但最终计数结果依然错误。问题不在于“锁没起作用”,而在于你根本没有对「计数逻辑」这个整体进行原子保护。
关键缺陷出现在 saveFlow() 方法中这一典型模式:
MutableInteger newMinuteAcceptKey = new MutableInteger();
newMinuteAcceptKey.setValue(1);
MutableInteger oldMinuteAcceptKey = map.put(minuteAcceptKey, newMinuteAcceptKey); // 步骤①:写入新值
if (null != oldMinuteAcceptKey) {
newMinuteAcceptKey.setValue(oldMinuteAcceptKey.getValue() + 1); // 步骤②:读旧值 → 计算 → 写回新值
}这段逻辑看似合理,实则存在经典的 check-then-act(先检查后执行)竞态条件:
- 线程 A 执行 map.put(...) 后得到 oldMinuteAcceptKey;
- 此时线程 B 也执行了 map.put(...),覆盖了 A 刚刚写入的 newMinuteAcceptKey;
- 线程 A 继续执行 oldMinuteAcceptKey.getValue() + 1,并将结果赋给 自己持有的、已脱离 map 的对象 —— 这个更新完全丢失,因为 map 中当前关联的已是线程 B 的实例。
⚠️ 注意:ConcurrentHashMap 仅保证单个操作(如 get, put, remove)的线程安全性,绝不保证多个操作组合的原子性。synchronized 在 MutableInteger 上只保护该对象内部状态,但无法约束 map 容器中键值对的生命周期与引用关系。
✅ 正确解法:使用原子更新方法
Java 8+ 为 ConcurrentHashMap 提供了真正原子的复合操作 API,推荐优先使用 merge() 或 compute():
// ✅ 推荐:一行代码完成「若存在则累加,否则初始化为1」
map.merge(minuteAcceptKey, 1, Integer::sum);
// ✅ 等价写法(显式 lambda)
map.merge(minuteAcceptKey, 1, (oldVal, newVal) -> oldVal + newVal);
// ✅ 或使用 compute(更灵活,可处理 null)
map.compute(minuteAcceptKey, (key, oldValue) ->
(oldValue == null) ? 1 : oldValue + 1
);此时你甚至不再需要 MutableInteger —— Integer 是不可变对象,merge 内部会以原子方式完成整个读-改-写流程,无需外部同步。
? 改造后的 saveFlow() 示例(精简、安全、高效)
public static void saveFlow(Calendar c) {
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sdfHour = new SimpleDateFormat("yyyy-MM-dd HH");
SimpleDateFormat sdfMinute = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String dayKey = sdfDate.format(c.getTime());
String hourKey = sdfHour.format(c.getTime());
String minuteKey = sdfMinute.format(c.getTime());
// 原子递增:不存在则设为1,存在则+1
map.merge(minuteKey, 1, Integer::sum);
map.merge(hourKey, 1, Integer::sum);
map.merge(dayKey, 1, Integer::sum);
}? 关键总结
- ❌ 错误范式:put() + get() + 修改 → 天然竞态,无论内部是否加锁;
- ✅ 正确范式:merge() / compute() / computeIfAbsent() → 单次调用完成原子复合操作;
- ? ConcurrentHashMap ≠ “所有操作都自动线程安全”,它只保障自身数据结构一致性,业务逻辑的原子性必须由开发者通过合适 API 显式保证;
- ? 若需高性能高频计数,还可考虑 LongAdder(适用于独立计数器)或 AtomicInteger(配合 compute() 使用),但本场景下 merge(Integer::sum) 已是最优解。
遵循原子操作原则,才能真正驾驭并发编程的复杂性。










