首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
深度解析:读写锁为何强制互斥,而 ConcurrentHashMap 却能无锁读?
📅 2026/10/6 16:00:42
✍️ 爱科研究院
👁 阅读 3,247
一、从一道经典面试题说起面试中常会问到为什么ReentrantReadWriteLock要求读-写互斥为什么ConcurrentHashMap的get()可以不加锁还能和写并发执行如果“读写可以不互斥”那是不是意味着所有引用类型都可以这样优化要回答这些问题我们必须先理解并发编程中“中间状态”的真正含义再深入到两种截然不同的数据更新策略。二、读写锁究竟在防止什么样的“中间状态”2.1 单变量场景并没有幻数如果只修改一个变量即使不加锁只用volatile或CAS你只会读到旧值或新值之一绝不会出现一个凭空捏造的数字。例如volatile int balance 1000;// 写线程balance balance - 200;读线程在任何时刻读取balance结果要么是1000要么是800不会是一个奇怪的中间值。但这不意味着安全。真正的危险来自多个关联变量。2.2 多变量场景业务脏快照考虑一个账户有两个业务上必须同步更新的字段int total 1000; // 总额度int usable 1000; // 可用额度// 扣款业务写线程total total - 200; // ① 执行后 total800usable usable - 200; // ② 执行后 usable800写线程执行完 ① 但还未执行 ② 的瞬间读线程并发进入读到total 800,usable 1000这个组合在系统的业务逻辑中根本不应该存在。这就是并发编程中所说的中间状态、脏快照不是单个变量值出错而是一组变量的组合视图出现新旧混搭。ReentrantReadWriteLock的读写互斥就是通过阻止读操作在写操作“进行到一半”时进入来彻底屏蔽这种风险。2.3 双向互斥的必要性有读时阻塞写多个读线程正在享受一致的旧数据此时放写线程进入会立刻破坏这份一致性。有写时阻塞读写线程正在更新多个变量读线程此时进入必然读到部分更新、部分未更新的混乱视图。任意时刻要么全是读要么只有一个写这就是读写锁保证强一致性的根基。三、ConcurrentHashMap如何打破读写互斥JDK 7 的ConcurrentHashMap实现了读操作完全无锁写操作仅锁分段Segment读写可以高度并发。它的底气来自两个核心设计volatile可见性引用替换。3.1 大量 volatile 字段保证可见性关键字段全部用volatile修饰static final class HashEntryK,V {final int hash;final K key;volatile V value;volatile HashEntryK,V next;}static final class SegmentK,V extends ReentrantLock {transient volatile HashEntryK,V[] table;transient volatile int count;}volatile保证写线程对这些字段的修改读线程能够立即感知不会长期滞留在本地缓存的老值。3.2 get() 的无锁流程定位 Segment读取volatile table拿到最新桶数组。沿链表通过volatile next遍历找到目标节点。读取volatile value返回。全程只有读取不涉及任何锁。3.3 为什么不会读到脏数据答案在于ConcurrentHashMap的更新从不原地修改节点内部内容而是采用新建节点 替换引用插入/删除时会构造新的链表结构新节点或跳过旧节点最后通过 CAS 或锁一次性将哈希桶的volatile引用指向新链表。旧链表结构保持完整丝毫不变。因此读线程遍历链表时要么看到完整的旧链表要么看到完整的新链表。绝不会出现一个链表断裂、循环或半新半旧的中间态。视图只有“旧完整快照”和“新完整快照”两种。代价可能读到稍旧的数据弱一致性但容器绝不会崩溃。这是ConcurrentHashMap明确接受的设计取舍。四、本质分界线原地修改 vs 替换引用很多人会误以为“只要用引用类型、加volatile并发读写就安全。”这是错误的。4.1 引用类型 ≠ 安全看一个反例class Account {int total;int usable;}volatile Account acc;// 写线程原地修改对象内部字段Account obj acc;obj.total - 200; // 步骤1obj.usable - 200; // 步骤2尽管acc是volatile引用但写线程没有改变引用本身而是直接修改了引用指向对象的内部字段。此时读线程并发进入依然会看到total已改、usable未改的错乱状态。4.2 真正的安全分界线更新策略行为并发读风险典型案例原地修改修改对象内部字段引用不变直接改动对象里的属性可能读到部分更新、部分未更新的混合中间态ReentrantReadWriteLock保护的多数业务代码替换引用新建对象切换指针不碰旧对象构建全新对象后原子替换 volatile 引用只有旧完整状态或新完整状态两种视图ConcurrentHashMap链表更新原地修改在原有文档上直接擦写旁人随时可能看到修改了一半的混乱内容。替换引用重新打印一份新文档最后换掉共享区的链接旁人要么取到旧文档要么取到新文档永远不会取到半成品。4.3 为什么 ReentrantReadWriteLock 不用“替换引用”方案因为ReentrantReadWriteLock是通用锁工具。它无法限制程序员在锁内部编写什么样的逻辑。程序员完全可以在临界区内执行原地修改、多次 I/O、复合计算等任意操作。为了保证任何场景下都不出现并发脏数据只能采用最保守、最通用的方案读写互斥。而ConcurrentHashMap作为一个高度定制的容器其设计者可以从底层保证更新方式一定是引用替换从而开辟出读写并发的高性能路径。五、一张表看清所有差异维度ReentrantReadWriteLockConcurrentHashMap (JDK 7/8)读写能否并发互斥防止读到中间状态可以读操作完全无锁更新模式通用业务代码多为原地修改引用替换原子发布新版本中间状态风险多个变量更新不同步链表结构永不断裂一致性级别强一致性弱一致性允许读到旧数据实现保证volatile 队列策略volatile 不可变节点 CAS/锁适用场景通用业务逻辑保护高性能容器高并发读写六、总结读写锁为什么互斥写操作往往涉及多个关联变量中途并发读取会拿到业务上非法的“新旧混搭”状态必须互斥。CHM 为什么能无锁读所有写操作都用新建对象 替换 volatile 引用实现读操作只会看到完整旧或完整新链表绝无中间态。本质区别不在于是否引用类型而在于更新策略——原地修改 vs 替换引用。Hashtable读写全锁并发极差ConcurrentHashMap以弱一致性换高吞吐。JDK 8同样延续 volatile 引用替换思路get()依然无锁。七、延伸思考数据库 InnoDB 的 MVCC 为什么能实现读写不阻塞MVCC 通过 undo 日志构建历史快照读操作可以读取适当时机的老版本这与ConcurrentHashMap“替换引用保留旧版本”的思路有异曲同工之妙。而ReentrantReadWriteLock作为悲观锁没有任何版本机制只能选择阻塞。理解这三种模型你对并发一致性的认知就完整了。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/6 16:00:42
VueUse useSortable 实战指南:在 Vue 3 中轻松实现列表拖拽排序
2026/10/6 16:00:42
Warp 通用技能(Common Skills)安装机制深度解析:以 `skills-lock.json` 为单一事实来源的 Agent 技能管理
2026/10/6 16:00:42
达梦数据库-报错-14-EMFILE error! The per-process limit of open file descriptors has been reached.
2026/10/6 18:36:24
10.【Linux系统编程】缓冲区详解——库缓冲区 VS 内核缓冲区
2026/10/6 18:36:24
Vue DevTools 完全指南:为 Vue.js 应用解锁开发者体验的调试工具链
2026/10/6 18:36:24
CCG Workflow 双模型交叉审查指南:/ccg:spec-review 命令全流程实战
2026/10/6 18:36:24
JointJS 自定义元素工具实战:用 dia.ToolView 实现 Port 拖拽重排
2026/10/6 18:36:24
Rust by Example 日文版翻译指南:从 fork 到合入上游的完整流程与术语规范
2026/10/6 18:31:23
从零设计STM32最小系统扩展板:原理图、PCB布局到打样焊接全流程
2026/10/6 1:04:29
搭建无线EEG采集前端:BW16+ESP32-CYD实时波形显示实战
2026/10/6 1:04:29
CH10D功放芯片DIY音箱实战:从选型到调试的完整指南
2026/10/6 1:04:29
视频序列目标跟踪实战:解决ID跳变与遮挡丢失
2026/10/6 15:41:36
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/6 4:47:52
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/6 13:15:25
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/5 20:28:25
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/5 20:28:23
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/5 20:28:21
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)