Java 并发集合:从实现细节到设计哲学

并发问题模型 → 设计策略 → 具体实现 的稳定知识体系。

第一性原理:为什么需要并发集合?

并发集合不是“线程安全的集合”这么简单,但也不是“根本矛盾的解决者”。准确定位它的角色,先要分清:哪些是问题、哪些是需求、哪个才是矛盾。

四个并发关切,三种范畴

普通集合面对的四个并发关切本不同范畴——混称“矛盾”会掩盖各自的可解性:

并发关切 范畴 本质 可解性
数据竞争 问题 多线程读写共享状态 有确定解:原子性 + 可见性
线程协作 需求 生产者与消费者步调不一致 可满足:阻塞 / 唤醒 / 解耦
顺序与时间 需求 有序性、延迟执行 可满足:时间语义表达
读写冲突 矛盾 并发 ⇄ 一致性 / 性能 不可消解,只可取舍

前三者有解或可满足;唯有“读写冲突”是本体张力——遍历的“稳定视图”与“修改即时生效”不可兼得

并发集合的角色:封装解,取舍矛盾

消解性的手段不在集合层:原子与可见来自 JMM happens-before,原子变更来自 CAS,阻塞唤醒来自 park/unpark 与 Condition(见 并发工具类.html;JMM 与 CAS 原理见 JAVA内存模型.html)。并发集合只做两件事:

并发集合承载并取舍矛盾,而不消解矛盾——它不是并发问题的解的发明者,而是解的容器化封装与场景化取舍。

并发集合的核心设计哲学

一致性是取舍

并发集合对“读取一致性”的全部处理,可归结为一根轴加一条分配律:一致性强度与并发度此消彼长,构成一根取舍轴;轴上的选点不按容器统一发放,而按操作粒度逐一计价。这根轴在两个层次上分别决策——先在容器级选立场,再在操作级分配强度(后者依赖前者,非平行维度):

层次 回答的问题 取值 支配律
容器级 读写并发时整体取哪种视图 禁止 / 中止 / 容纳 一致性 ⇄ 并发 不可兼得
操作级 同一容器各操作给多强 单键强 / 聚合迭代弱 强度对齐同步成本

分配律:一致性强度按操作的代价分配。单键读写已有 happens-before,强一致几乎没有额外的代价,于是给足;聚合(size)和迭代要强一致就得加全局锁,太贵,于是降级为弱一致换吞吐。这与「读优先假设」「最小共享原则」一脉相承——只在必要处付出协调成本。

同一根取舍轴在单机与分布式上都成立:容器层的禁止 / 中止 / 容纳,对应分布式的线性 → 因果 → 最终一致,以及 CAP/PACELC 的一致性 ⇄ 可用性。。

读优先假设

大量并发容器默认假设:读远多于写

之所以押注“读多写少”:读-读天然不冲突、同步成本只为冲突而付,故押注读占主导可把全部协调成本单边转嫁给稀疏的写、让高频读走无锁快路径,期望收益最大(边界:写频升高则此押注反噬)。

由此演化出两条主线:

最小共享原则

典型体现:

并发容器的演进主线——锁粒度沿「全表锁 → 分段 → 节点锁 + CAS」单调收缩——正是「减少共享」这一通用规律在容器领域的投影。规律本身(消除型/协调型控制、共享面收缩、并发⇄一致性取舍、隔离→乐观→互斥的优先级)见 并发编程.html「并发控制哲学 / 现代并发演化」。

所有权转移而非共享

转移所有权的价值在于走消除型控制——任一刻单一持有,竞态三要素中的"并发访问"被直接去除,对象因而免同步;这是「最小共享原则」推到极限(零共享)的形态。

在队列模型中:

阻塞队列是一种串行线程封闭(Serial Thread Confinement)的实现形式。

并发集合的分类:载体 × 灵魂

并发集合 = 载体(组织何种数据结构)× 灵魂(用何种策略消解竞态)

载体轴:组织何种结构

载体 存取形态 代表
Map 键值映射 ConcurrentHashMapConcurrentSkipListMap(有序)
Set 去重集合 ConcurrentSkipListSetCopyOnWriteArraySet
List 索引顺序表 CopyOnWriteArrayList
Queue 单端进出 ConcurrentLinkedQueueBlockingQueue
Deque 双端进出 ConcurrentLinkedDequeLinkedBlockingDeque

灵魂轴:用何种策略消解竞态

竞态 = 共享 + 可变 + 并发访问,消解任一条即安全(见 并发编程.html 消除型 / 协调型控制)。并发集合的安全策略按此分三类:

策略 消解 范式 投影
不共享 去掉“共享”——所有权转移 串行线程封闭 Queue 家族
不可变 去掉“可变”——对读者冻结 快照 / 写时复制 COW 系
加秩序 管控“并发访问” 乐观 CAS / 悲观锁分段 CHM、SkipListMap、队列内部

生成矩阵:用组合预测存在性

载体 × 灵魂张成矩阵,空格本身即洞察:

载体\灵魂 加秩序(CAS/锁) 不可变(COW) 不共享(转移)
Map CHM、SkipListMap (罕见)
List (罕见,锁 List) CopyOnWriteArrayList
Set SkipListSet CopyOnWriteArraySet
Queue BlockingQueue 内部 本即转移语义

空格不是在说明“这个组合为何不存在”:

空格 缺席原因(即洞察)
非 Queue × 不共享(转移) 转移只对“流经的元素”成立;Map/List/Set 是驻留型容器,无所有权转移语义 → 转移是 Queue 专属
Map × 不可变(COW) 罕见 Map 写按 key 局部更新,COW 须整表复制、写放大不可接受 → JDK 无 CopyOnWriteMap
List × 加秩序 罕见 List 随机写难以分段,锁分段收益低 → 读多写少 List 的需求被 COW 占据
Queue × 不可变(COW) 队列元素不断进出,无“稳定迭代视图”需求 → COW 对队列不成立

派生属性归位:现象 → 根因

可观测现象 根因(哪个决策的投影)
阻塞 / 非阻塞 载体 = Queue 且灵魂含 Condition 协作
有界 / 无界 Queue 的容量约束(流控需求)
聚合弱一致 灵魂 = 加秩序且无全局锁 的必然后果
迭代快照一致 灵魂 = 不可变 / COW 的必然后果
排序 / 延迟 载体 = 优先队列,延迟是按到期时间排序的特例

设计策略层:容器如何复用原语

原语本身——CAS、锁与公平性、park/Condition——的原理在 /编程语言/JAVA/JVM/JAVA内存模型.html/编程语言/JAVA/JAVA并发编程/并发工具类.html。本章只看容器特有的增量:把通用原语贴合到数据结构上的复用技术。三族灵魂各对应一组。

加秩序:把锁粒度贴合结构

容器的增量是让协调成本随结构分布,而非锁住整体:

容器化技术 机制 代表
分段 / 节点锁 锁粒度 = 单桶 / 单节点,竞争被散列打散 ConcurrentHashMap
双锁分离 队头队尾各一把锁,入队与出队互不阻塞 LinkedBlockingQueue(putLock/takeLock)
CAS 无锁链表 入队出队仅 CAS 头尾指针,零锁 ConcurrentLinkedQueue
扩容协作 多线程并发迁移桶,迁移期读写不停 ConcurrentHashMap resize

这些是「最小共享原则」的落地:锁粒度沿「全表 → 分段 → 节点 + CAS」单调收缩。

不可变:写时复制

容器特有,无对应 JUC 工具——核心是把“对读者冻结”做成机制:

读到的恒是某次替换的完整快照——迭代天然一致、不抛 CME。代价是写放大与内存翻倍,故前提是写少读多。本质是“以空间换无锁读”。

不共享:所有权转移的协作点

容器把阻塞 / 唤醒原语封装为队满则等、队空则等的节流阀,put/take 即所有权交接点——机制已在「所有权转移」节阐明,实现细节见 BlockingQueue 族。

并发容器选型方法论

先问问题,而不是选类

决策表:场景 → 实例

场景特征 推荐
高并发读写 Map ConcurrentHashMap
需有序 / 范围查询的 Map ConcurrentSkipListMap
读极多写极少 List CopyOnWriteArrayList
高并发非阻塞队列 ConcurrentLinkedQueue
阻塞 / 限流 / 交接 / 调度 BlockingQueue 族(见下「容量轴」)

代价对照:失效模式即选用边界

实例 载体 × 灵魂 核心代价 / 失效模式
ConcurrentHashMap Map × 加秩序 size/聚合弱一致,仅供估算
ConcurrentSkipListMap 有序 Map × 加秩序 单点 O(log n) vs O(1)
CopyOnWriteArrayList List × 不可变 写放大 + 内存翻倍,写 O(n)
ConcurrentLinkedQueue Queue × 不共享(CAS) 无阻塞语义、size() O(n)、无界有 OOM 风险

BlockingQueue:容量轴即协作语义

家族的第一区分轴是容量,容量值直接映射协作语义,其余差异(单 / 双锁)是该轴的实现派生:

容量 实例 协作语义 触发场景 代价 / 失效模式
0 SynchronousQueue 直接交接,无缓冲 线程间一对一移交 无消费者时 offer 立即失败、put 阻塞
有界固定 ArrayBlockingQueue 背压限流 严格流控 满则生产者阻塞 / 拒绝;容量预分配
可选(默认无界) LinkedBlockingQueue 缓冲削峰 流量波动 默认无界 → OOM 风险(同 ConcurrentLinkedQueue
时间 / 优先序 DelayQueuePriorityBlockingQueue 按到期 / 优先级出队 定时调度 仅到期元素可取,poll 可能返回 null 即便非空;无界 → OOM

容量 0 → 交接、有限 → 限流、无限 → 缓冲——一根轴解释整个家族的存在理由。

并发集合的终极认知

并发集合不发明并发的解,只在两根轴上做场景化取舍——如何消解竞态一致性给到多强。这两轴跨语言、跨单机与分布式不变;记轴,而非类名。

稳定洞察 内核 跨尺度同构
安全策略由竞态决定 竞态三要素(共享 / 可变 / 并发访问)→ 三族策略(不共享 / 不可变 / 加秩序),即消除型 vs 协调型 锁、事务、分布式并发控制同此三分
一致性是取舍轴 强度按成本分配——廉价处给足、昂贵处降级 分布式 线性 → 因果 → 最终一致、CAP / PACELC
代价是策略的影子 选定灵魂即选定代价:不可变必写放大、无锁聚合必弱一致 任何乐观 / 复制方案的固有税
转移即孤岛 所有权转移 = 共享内存模型里的一座消息传递孤岛 Actor 邮箱、Go channel、CSP

两轴生成全部容器:竞态消解策略 × 一致性取舍点。其余一切——阻塞、有界、有序、乃至类名——都是这两轴在具体载体上的投影。

关联内容(自动生成)