并发问题模型 → 设计策略 → 具体实现 的稳定知识体系。
并发集合不是“线程安全的集合”这么简单,但也不是“根本矛盾的解决者”。准确定位它的角色,先要分清:哪些是问题、哪些是需求、哪个才是矛盾。
普通集合面对的四个并发关切本不同范畴——混称“矛盾”会掩盖各自的可解性:
| 并发关切 | 范畴 | 本质 | 可解性 |
|---|---|---|---|
| 数据竞争 | 问题 | 多线程读写共享状态 | 有确定解:原子性 + 可见性 |
| 线程协作 | 需求 | 生产者与消费者步调不一致 | 可满足:阻塞 / 唤醒 / 解耦 |
| 顺序与时间 | 需求 | 有序性、延迟执行 | 可满足:时间语义表达 |
| 读写冲突 | 矛盾 | 并发 ⇄ 一致性 / 性能 | 不可消解,只可取舍 |
前三者有解或可满足;唯有“读写冲突”是本体张力——遍历的“稳定视图”与“修改即时生效”不可兼得
消解性的手段不在集合层:原子与可见来自 JMM happens-before,原子变更来自 CAS,阻塞唤醒来自 park/unpark 与 Condition(见 并发工具类.html;JMM 与 CAS 原理见 JAVA内存模型.html)。并发集合只做两件事:
并发集合承载并取舍矛盾,而不消解矛盾——它不是并发问题的解的发明者,而是解的容器化封装与场景化取舍。
并发集合对“读取一致性”的全部处理,可归结为一根轴加一条分配律:一致性强度与并发度此消彼长,构成一根取舍轴;轴上的选点不按容器统一发放,而按操作粒度逐一计价。这根轴在两个层次上分别决策——先在容器级选立场,再在操作级分配强度(后者依赖前者,非平行维度):
| 层次 | 回答的问题 | 取值 | 支配律 |
|---|---|---|---|
| 容器级 | 读写并发时整体取哪种视图 | 禁止 / 中止 / 容纳 | 一致性 ⇄ 并发 不可兼得 |
| 操作级 | 同一容器各操作给多强 | 单键强 / 聚合迭代弱 | 强度对齐同步成本 |
分配律:一致性强度按操作的代价分配。单键读写已有 happens-before,强一致几乎没有额外的代价,于是给足;聚合(size)和迭代要强一致就得加全局锁,太贵,于是降级为弱一致换吞吐。这与「读优先假设」「最小共享原则」一脉相承——只在必要处付出协调成本。
同一根取舍轴在单机与分布式上都成立:容器层的禁止 / 中止 / 容纳,对应分布式的线性 → 因果 → 最终一致,以及 CAP/PACELC 的一致性 ⇄ 可用性。。
大量并发容器默认假设:读远多于写
之所以押注“读多写少”:读-读天然不冲突、同步成本只为冲突而付,故押注读占主导可把全部协调成本单边转嫁给稀疏的写、让高频读走无锁快路径,期望收益最大(边界:写频升高则此押注反噬)。
由此演化出两条主线:
典型体现:
并发容器的演进主线——锁粒度沿「全表锁 → 分段 → 节点锁 + CAS」单调收缩——正是「减少共享」这一通用规律在容器领域的投影。规律本身(消除型/协调型控制、共享面收缩、并发⇄一致性取舍、隔离→乐观→互斥的优先级)见 并发编程.html「并发控制哲学 / 现代并发演化」。
转移所有权的价值在于走消除型控制——任一刻单一持有,竞态三要素中的"并发访问"被直接去除,对象因而免同步;这是「最小共享原则」推到极限(零共享)的形态。
在队列模型中:
阻塞队列是一种串行线程封闭(Serial Thread Confinement)的实现形式。
并发集合 = 载体(组织何种数据结构)× 灵魂(用何种策略消解竞态)
| 载体 | 存取形态 | 代表 |
|---|---|---|
| Map | 键值映射 | ConcurrentHashMap、ConcurrentSkipListMap(有序) |
| Set | 去重集合 | ConcurrentSkipListSet、CopyOnWriteArraySet |
| List | 索引顺序表 | CopyOnWriteArrayList |
| Queue | 单端进出 | ConcurrentLinkedQueue、BlockingQueue 族 |
| Deque | 双端进出 | ConcurrentLinkedDeque、LinkedBlockingDeque |
竞态 = 共享 + 可变 + 并发访问,消解任一条即安全(见 并发编程.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 风险 |
家族的第一区分轴是容量,容量值直接映射协作语义,其余差异(单 / 双锁)是该轴的实现派生:
| 容量 | 实例 | 协作语义 | 触发场景 | 代价 / 失效模式 |
|---|---|---|---|---|
| 0 | SynchronousQueue |
直接交接,无缓冲 | 线程间一对一移交 | 无消费者时 offer 立即失败、put 阻塞 |
| 有界固定 | ArrayBlockingQueue |
背压限流 | 严格流控 | 满则生产者阻塞 / 拒绝;容量预分配 |
| 可选(默认无界) | LinkedBlockingQueue |
缓冲削峰 | 流量波动 | 默认无界 → OOM 风险(同 ConcurrentLinkedQueue) |
| 时间 / 优先序 | DelayQueue、PriorityBlockingQueue |
按到期 / 优先级出队 | 定时调度 | 仅到期元素可取,poll 可能返回 null 即便非空;无界 → OOM |
容量 0 → 交接、有限 → 限流、无限 → 缓冲——一根轴解释整个家族的存在理由。
并发集合不发明并发的解,只在两根轴上做场景化取舍——如何消解竞态 与 一致性给到多强。这两轴跨语言、跨单机与分布式不变;记轴,而非类名。
| 稳定洞察 | 内核 | 跨尺度同构 |
|---|---|---|
| 安全策略由竞态决定 | 竞态三要素(共享 / 可变 / 并发访问)→ 三族策略(不共享 / 不可变 / 加秩序),即消除型 vs 协调型 | 锁、事务、分布式并发控制同此三分 |
| 一致性是取舍轴 | 强度按成本分配——廉价处给足、昂贵处降级 | 分布式 线性 → 因果 → 最终一致、CAP / PACELC |
| 代价是策略的影子 | 选定灵魂即选定代价:不可变必写放大、无锁聚合必弱一致 | 任何乐观 / 复制方案的固有税 |
| 转移即孤岛 | 所有权转移 = 共享内存模型里的一座消息传递孤岛 | Actor 邮箱、Go channel、CSP |
两轴生成全部容器:竞态消解策略 × 一致性取舍点。其余一切——阻塞、有界、有序、乃至类名——都是这两轴在具体载体上的投影。