concurrenthashmap需按设计逻辑使用:读不加锁、写用原子方法、初始化预估容量、遍历接受弱一致;错误使用复合操作会导致数据丢失,正确使用可支撑百万级qps。

ConcurrentHashMap 不是“能用就行”的线程安全容器,而是需要按它的设计逻辑来用——读不加锁、写靠原子方法、初始化要预估、遍历接受弱一致。用对了,百万级 QPS 也能稳住;用错了,照样丢数据、性能崩。
只用原子方法做复合操作
像 if (!map.containsKey(k)) map.put(k, v) 这类两步判断+写入,在多线程下必然出错:两个线程同时判定 key 不存在,接着都 put,后写直接覆盖前写。ConcurrentHashMap 不会帮你串行化这种逻辑。
- putIfAbsent(k, v):key 不存在才插入,一次 CAS 完成,安全可靠
- computeIfAbsent(k, f):key 不存在时调用函数生成值,函数体仅由一个线程执行(适合查库加载缓存)
- merge(k, v, (old, new) -> old + new):安全累加计数器,避免手动 get + put
- replace(k, oldValue, newValue):带旧值校验的更新,防止并发误覆盖
读操作别加锁,直接 get 就行
get() 是无锁操作,依赖 volatile 字段和内存屏障保证可见性,性能几乎和 HashMap 拉齐。在缓存、配置中心等读多写少场景中,map.get(key) 就是最佳写法。
千万别因为“怕不安全”就给 get 加 synchronized 或 ReentrantLock——这会让所有读请求排队,吞吐量断崖式下跌,反而成了性能瓶颈。
初始化容量要按实际预估并向上取整
默认初始容量是 16,但百万级并发下频繁扩容会触发协作式 rehash,写线程需参与迁移,CPU 和 GC 压力陡增,甚至引发短暂卡顿或 OOM。
- 预估总键数(比如 180 万),向上取整到最近的 2 的幂次 → 221 = 2097152
- 构造时显式传入:
new ConcurrentHashMap(2097152) - 负载因子保持默认 0.75 即可;调大(如 0.9)会导致哈希冲突加剧,链表变长,影响性能
遍历时接受弱一致性,不强求实时准确
ConcurrentHashMap 的迭代器是弱一致的:不会抛 ConcurrentModificationException,但可能漏掉刚插入的项,或看到已删除项的旧值。
这对监控统计、日志采集、后台导出等场景完全适用——你本来就不需要毫秒级精确的快照。强行用外部同步或复制全量数据,既没意义又拖慢系统。
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











