concurrenthashmap保证单个操作(如put、get、computeifabsent)的线程安全,但多步组合操作(如先检查后执行)不原子,需用putifabsent、compute或merge替代;迭代器弱一致性,不抛concurrentmodificationexception;禁用synchronized(map)和误用size()。

ConcurrentHashMap 是 Java 中专为高并发场景设计的线程安全哈希表,它不靠全局锁,而是通过分段锁(JDK 7)或 CAS + synchronized 细粒度加锁(JDK 8+)来提升并发性能。正确使用它,关键在于理解它的线程安全边界和常见误区。
它保证哪些操作是线程安全的?
ConcurrentHashMap 明确保证单个操作(如 put、get、remove、computeIfAbsent、merge)的原子性。例如:
-
map.put("key", "value")是线程安全的; -
map.get("key")总能读到已成功写入的最新值(满足 happens-before,但不保证强一致性——可能读到“稍旧”的值); -
map.computeIfAbsent("key", k -> heavyInit())确保初始化逻辑只执行一次,即使多个线程同时触发。
哪些操作不是原子的?必须自己加同步
多步组合操作(即“先检查再执行”类逻辑)不自动线程安全,比如:
-
if (!map.containsKey("k")) map.put("k", "v");—— 存在竞态:两个线程都判断不存在,然后都 put,导致覆盖或重复初始化; -
map.put("k", map.get("k") + 1);—— get 和 put 是两次独立调用,中间可能被其他线程修改。
✅ 正确做法是使用内置原子方法替代:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 用
map.putIfAbsent("k", "v")替代“if not exists then put”; - 用
map.compute("k", (k, v) -> (v == null ? 0 : v) + 1)或map.merge("k", 1, Integer::sum)实现安全计数; - 对复杂逻辑,可考虑
map.computeIfAbsent("k", k -> initExpensiveObject())。
遍历时的安全注意事项
ConcurrentHashMap 的迭代器是弱一致性(weakly consistent)的:
- 不会抛
ConcurrentModificationException; - 可反映创建迭代器之后的结构变更(如新增元素),但不保证看到所有变化;
- 不支持
iterator.remove()(会抛UnsupportedOperationException)。
✅ 安全遍历方式:
- 直接 for-each:
for (Map.Entry<string v> e : map.entrySet()) { ... }</string>; - 需要删除时,用
map.keySet().removeIf(key -> condition)或map.forEach((k, v) -> {...}); - 若需严格快照语义(如统计全量),可先
new HashMap(map)再遍历(注意内存与一致性权衡)。
不推荐的用法
以下做法看似“更安全”,实则破坏 ConcurrentHashMap 的设计优势,甚至引入死锁或性能瓶颈:
-
synchronized (map) { map.put(...); }—— 锁住整个 map,退化为串行; - 把 ConcurrentHashMap 当作普通 Map 传给要求
java.util.Map的工具方法,而该方法内部做了非原子批量操作(如Collections.synchronizedMap包装后再传入); - 误用
size()判断是否为空:它返回的是近似值(JDK 8+),高并发下可能不准;应改用map.isEmpty()(该方法是原子且准确的)。
ConcurrentHashMap 不是万能锁替代品,它的价值在于用最小同步代价支撑高频读写。用对方法、避开复合逻辑陷阱,才能真正发挥其并发优势。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










