跳到正文
Elaine Blog
返回

并发 Map、List 和 Set 的实现权衡

并发编程

并发集合不是给普通集合外面包一把锁。它们对读写竞争、迭代一致性、排序和快照采用不同策略。选择前先写清读写比例、 元素数量,以及一次操作是否需要跨多个键保持原子。

Table of contents

Open Table of contents

ConcurrentHashMap 适合高并发键值访问

ConcurrentHashMap 允许并发读取和更新,并提供单键原子复合操作:

var counts = new java.util.concurrent.ConcurrentHashMap<String, java.util.concurrent.atomic.LongAdder>();
counts.computeIfAbsent("success", ignored -> new java.util.concurrent.atomic.LongAdder()).increment();

不要写 if (!map.containsKey(key)) map.put(key, value),检查和写入之间存在竞态。使用 putIfAbsentcomputemerge 等原子 API。

映射的一次单键操作线程安全,不代表多个键的业务事务自动原子。账户 A 扣款与账户 B 入账仍需要更高层锁、数据库事务 或事件状态机。

弱一致迭代避免全局停顿

并发集合迭代器通常是**弱一致(Weakly Consistent)**的:不会抛 ConcurrentModificationException,可以反映部分并发 更新,但不承诺某个瞬间的完整快照。

若报表要求严格快照,在业务层建立版本、锁或复制边界。不要因为一次迭代“看起来包含全部数据”就依赖未声明的顺序。

CopyOnWrite 适合读多写极少

CopyOnWriteArrayList 每次写入都复制整个底层数组,迭代器则读取创建时的稳定快照。它适合元素较少、读远多于写的监听器 列表或配置订阅者。

以下场景不适合:大型列表、频繁写入、批量更新、元素引用本身可变。容器快照只固定引用数组,不会把元素深度复制成不可变。

有序并发结构

ConcurrentSkipListMap 按键排序并支持范围查询,操作通常为 O(log n)。需要排序视图时它比“ConcurrentHashMap 加全量 排序”更自然,但常数成本和内存占用更高。

并发 Set 可以使用 ConcurrentHashMap.newKeySet()。若需要排序集合,使用 ConcurrentSkipListSet。选择 Set 前仍要确保 元素的 equals/hashCode 或比较器在元素生命周期内稳定。

选型表

工作负载建议起点
高频按键读写ConcurrentHashMap
少量监听器,写极少CopyOnWriteArrayList
按键排序与范围查询ConcurrentSkipListMap
高竞争统计计数Map + LongAdder
严格多键事务显式同步或外部事务系统

下一步

阅读BlockingQueue 与生产者消费者,用有界容量把消费能力反馈给生产方。


分享这篇文章:

上一篇
Semaphore、CountDownLatch 与 CyclicBarrier
下一篇
BlockingQueue 与生产者消费者