← ReJava · 学习目录

REJAVA / LEARNING NOTES

第6课速查:ConcurrentHashMap(并发哈希映射) 七问

随时回查术语、规则和示例;完整解释见对应课程。

Java 17 / 21更新于 2026.10.12约 3 分钟阅读

根据课程材料整理

本次更新说明
  • 页尾去掉本地资料入口。
  • 按新版第6课改为七个要点。
  • 整理为公开学习笔记,保留技术内容与练习。
  • 清理个人分析,完善阅读导航。

1 · 线程安全,为什么还少算?

它只保证 单次方法调用 原子。get(读取方法) 再 put(写入方法) 是两次调用:两线程读到同一旧值,后写覆盖先写 → lost update(更新丢失)。和 i++ 同类问题。

回看

2 · 怎么改?

计数:merge(k, 1, Integer::sum),返回新值。

putIfAbsent(不存在才放入的方法):值先造好,返回旧值或 null(空值)。

computeIfAbsent(缺失时计算的方法):不存在才执行函数,返回现值。

compute(原子重算方法):按当前值(可能 null(空值))算新值。

函数要短,不调远程接口,不改本表;函数返回 null(空值):computeIfAbsent(缺失时计算的方法) 不放入,compute(原子重算方法)/merge(原子合并方法) 删除该 key(键)。

回看

3 · 还要自己管什么?

value(值) 是 ArrayList(可变数组列表) 时的 add(追加方法);跨两个 key(键) 一起生效;先查 size(数量方法) 再插入;遍历不是快照(弱一致,不抛 ConcurrentModificationException(并发修改异常))。

put(写入方法) 之前的写,对 get(读取方法) 到该值的线程可见;入表后再改字段不保证。

回看

4 · JDK 7 vs JDK 8

7: Segment(分段锁) 分段,每段一把 ReentrantLock(可重入锁),默认 16 段。

8+: Node 数组 + 链表/红黑树;空桶 CAS(比较并交换);非空桶 synchronized(内置同步关键字) 锁桶头;get(读取方法) 不加锁。

用 synchronized(内置同步关键字):首节点直接当锁,省去每桶一个锁对象。

回看

5 · put(写入方法) 流程

null(空值) 检查 → spread 算 hash(哈希) → 循环:没表先建表/空桶 CAS(比较并交换)/遇转发节点先帮忙扩容/否则锁桶头,链表或树里替换或追加 → 链表 ≥ 8 时 treeifyBin(数组 < 64 先扩容)→ addCount 计数并检查扩容。

扩容:多线程分段协助迁移,迁完的旧桶放 forwarding node(转发节点)。

回看

6 · 为什么不许 null(空值)

避免歧义:get(读取方法) 返回 null(空值) 分不清“没有”还是“值为 null(空值)”;并发下无法再用 containsKey 补判。禁止后,null(空值) 只表示没有。

回看

7 · size(数量方法) 准不准

baseCount + CounterCell(计数单元) 数组(LongAdder(分散累加器) 思路):无竞争 CAS(比较并交换) 改 baseCount,有竞争分散到格子。size(数量方法) 求和,并发下是估计值;推荐 mappingCount(返回 long)。

回看

返回完整课程 · 课后练习 · 官方依据与源码 · 课程目录