# 《Java并发编程的艺术》知识点全解 > AI生成|2026年10月28日|codebuddy|原书:方腾飞 魏鹏 程晓明《Java并发编程的艺术》,机械工业出版社,2015-07-01 > **我的阅读**:进度 100%|累计 19小时57分钟|开始 2017-04-14|最近 2024-05-23|读完 2017-11-07 **一句话总结**:把「并发为何不安全」从 CPU 缓存、内存屏障、JVM 内存模型一路推到 AQS、并发容器与线程池的实现,让你写 `volatile`、`synchronized`、`ThreadPoolExecutor` 时知道背后发生了什么,能判断而不是背诵。 **适合谁**:用过线程池但停留在「加锁就安全」的中级开发者;要讲清 JMM 与 AQS 的面试者;反复读 JDK 并发源码却读不下去的人。 **不适合谁**:零 Java 基础(对象头、内部类会吃力);只想要 API 速查者;需要虚拟线程者(成书 2015 年,以 JDK 7 为主)。 **前置知识**:Java 语法与集合框架、`Thread`/`Runnable` 用法、对原子性/可见性/有序性有模糊印象。 ## 一、全书地图 全书是「问题→硬件→规范→工具→实践」的纵深链,并非并列: | 章节 | 主题 | 在链路中的位置 | |---|---|---| | 第1章 并发编程的挑战 | 上下文切换、死锁、资源限制 | 提出问题:并发为何更慢更危险 | | 第2章 底层实现原理 | volatile、synchronized、原子操作的 CPU 与字节码实现 | 硬件层答案,第5章地基 | | 第3章 Java内存模型 | 主内存与工作内存、happens-before、重排序、final | 理论核心,解释全书的可见性 | | 第4章 并发编程基础 | 线程状态、中断、等待通知、ThreadLocal | 规范落到 API | | 第5章 Java中的锁 | 锁语义、AQS、ReentrantLock、读写锁、Condition | 第2章原理的工程化 | | 第6章 并发容器和框架 | ConcurrentHashMap、阻塞队列、Fork/Join | 锁能力的复用升级 | | 第7章 13个原子操作类 | CAS 封装成的无锁工具 | 第2章 CAS 的 API 化 | | 第8章 并发工具类 | CountDownLatch、CyclicBarrier、Semaphore、Exchanger | 基于第5章 AQS 的同步器 | | 第9章 线程池 | ThreadPoolExecutor 参数、流程、拒绝策略 | 线程与队列的组装 | | 第10章 Executor框架 | 提交与执行解耦、Future、定时、ForkJoinPool | 第9章的上层抽象 | | 第11章 并发编程实践 | 生产消费、线上定位、性能调优 | 收口排障 | 串记:**1 问难,2 看底层,3 定规则,4 用线程,5 造锁,6-8 用轮子,9-10 管执行,11 上生产。** ## 二、核心知识点 **1. 上下文切换**:轮转线程要保存现场并使缓存、TLB 失效,这是「并发不总更快」「线程池不能无限大」的依据。用 `vmstat`、`pidstat -w` 量化,靠无锁、合理线程数、缩短临界区来降。误区:把线程多等同吞吐高;锁阻塞引发的切换更贵。 **2. volatile 两层语义**:写立即刷回主内存并让他核缓存行失效(可见性),同时禁止前后重排序(有序性)。它是状态标志位与 DCL 的正确性基础,也是最便宜的一档同步。误区:以为能保证原子性,`i++` 仍不安全。 **3. synchronized 锁升级**:状态存在对象头 Mark Word——无竞争是偏向锁(记线程 ID,免 CAS);有竞争升轻量级锁(CAS 指向栈上 Lock Record 并自旋);失败升重量级锁(Monitor,进内核态等待队列)。这解释了「JDK 6 后 synchronized 不再慢」。低竞争直接用;需超时、可中断、公平、多条件才换 ReentrantLock。误区:以为升级可逆;在临界区做 IO。 **4. CAS 与 ABA**:`compareAndSwap` 由 CPU 指令保证原子,失败自旋。它是原子类、AQS、ConcurrentHashMap 的公共底座。ABA 敏感处用 `AtomicStampedReference` 加版本号。误区:忽略高竞争下自旋烧 CPU;忽略它只保证单变量原子。 **5. JMM 与 happens-before**:JMM 抽象主内存与线程工作内存。happens-before 是「前操作结果对后操作可见且不被重排」的偏序,含程序顺序、监视器锁、volatile、线程启动、线程终止、中断、对象终结与传递性。它是判断并发安全唯一的形式化工具:两线程间有边则安全,无边即数据竞争。误区:把「时间上先发生」当成 happens-before。 **6. 重排序与内存屏障**:编译器、指令级并行、内存系统三层都可能重排,JMM 用四类内存屏障限制。所有「读到半初始化对象」「标志位先于数据可见」的怪 bug 都源于此。跨线程可见就建立 happens-before,别用 `sleep` 凑时序。误区:以为 x86 强内存模型下可随便写,编译器重排照样存在。 **7. final 与 DCL**:构造器内对 final 域的写与随后把引用赋给其他变量禁止重排序,故正确构造的不可变对象可无同步共享。而 `new X()` 分三步——分配内存、初始化、引用赋值,后两步可重排,别人会拿到「非 null 但未初始化完」的对象,故 DCL 字段必须 volatile。更推荐静态内部类或枚举单例。误区:构造期让 `this` 逸出(注册监听器、启动线程),final 保证即失效。 **8. 线程状态与中断**:六态 NEW/RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED。`interrupt()` 只置标志,是否响应由被中断方决定(`stop()` 已废弃)。循环里查 `isInterrupted()`;捕获 `InterruptedException` 后要么上抛,要么 `Thread.currentThread().interrupt()` 恢复标志。误区:捕获后什么都不做(吞中断);用会清除标志的 `interrupted()` 反复判断。 **9. 等待通知与 ThreadLocal**:`wait` 必须持锁调用并释放锁,被唤醒后还要重新竞争锁;ThreadLocal 数据实存于 `Thread` 内的 `ThreadLocalMap`,key 弱引用、value 强引用。`wait` 必须包在 `while(!条件)` 里防虚假唤醒;ThreadLocal 用完 `remove()`。误区:用 `if` 代替 `while`;在线程池里存大对象不清理,key 被回收后 value 仍泄漏。 **10. AQS**:`volatile int state` + FIFO 双向队列(CLH 变体)+ 模板方法;子类实现 `tryAcquire/tryRelease` 或共享版,排队、阻塞、唤醒由 AQS 负责。ReentrantLock、Semaphore、CountDownLatch、读写锁全建在它上面。state 语义自定义(重入次数、剩余许可、倒计数)。误区:在 `tryAcquire` 里阻塞;忘了 state 修改需 CAS 或已在锁内。 **11. ReentrantLock、Condition 与读写锁**:前者提供可重入、公平/非公平、可中断、可超时;Condition 让一把锁有多个等待集(`await/signal`);读写锁用 state 高低 16 位分别计读锁与写锁。只支持写锁降级为读锁,不支持读锁升级。误区:以为公平锁更快(吞吐更低);`unlock()` 没放 finally。 **12. 并发容器**:ConcurrentHashMap 在 JDK 7 用 Segment 分段锁(默认 16 段),JDK 8 改为 CAS + synchronized + 链表/红黑树,锁细到桶;阻塞队列空/满时阻塞,是生产者—消费者与线程池任务缓冲的载体。读多写少用 `CopyOnWriteArrayList`;按有界、优先级、延迟性选队列。误区:以为 `size()` 精确(实为弱一致);复合操作用「先 get 再 put」而不用 `putIfAbsent`。 **13. 线程池与 Executor**:`ThreadPoolExecutor(core,max,keepAlive,queue,factory,handler)` 流程为「核心未满建核心 → 核心满入队 → 队满建到 max → 再满拒绝」。配错会 OOM(无界队列)、丢任务、CPU 打满;CPU 密集型≈核数+1,IO 密集型按「核数×(1+等待/计算)」估。上层用 `Future` 取结果、`CompletionService` 按完成序取,Fork/Join 靠工作窃取均衡负载。误区:`newCachedThreadPool` 的 `max=Integer.MAX_VALUE`;`submit` 的异常封在 Future 里,不 `get` 永远看不到;`get()` 不设超时。 ## 三、关键概念速查表 | 概念 | 一句话解释 | 用在哪儿 | |---|---|---| | 原子性/可见性/有序性 | 不可分割/写能被看到/不被重排 | 三大问题,所有手段都为其服务 | | happens-before | 定义跨线程可见性的偏序规则 | 审查代码是否安全 | | 内存屏障 | 禁止特定重排的 CPU 指令 | volatile 的底层实现 | | Mark Word | 对象头中存 hashCode、GC 分代、锁标志 | 锁升级的物质基础 | | 偏向/轻量级/重量级锁 | 免 CAS/CAS 自旋/Monitor 阻塞 | 按竞争程度自动选择 | | CAS | 比较并交换的 CPU 原子指令 | 原子类、AQS、CHM | | ABA 问题 | 值改回原值致 CAS 误判 | 版本号或 `AtomicStampedReference` | | AQS | state + FIFO 队列的同步器骨架 | 锁、信号量、倒计数门闩 | ## 四、可复用的方法/框架 **A. 安全审查四问**:① 几个线程读写?只有一个则无需同步。② 共享且可变?不可变则安全发布即可。③ 两线程间有 happens-before 边(锁、volatile、线程启停、并发容器)?没有即数据竞争。④ 有边时复合操作原子吗?否则加锁或换原子类。 **B. 同步手段选型**:线程封闭(局部变量/ThreadLocal)→ 不可变 → 原子类(单变量 CAS)→ synchronized(低竞争)→ ReentrantLock(需超时/中断/公平/多条件)→ 并发容器 → 线程池。越靠左越便宜。 **C. 线程池参数五步**:判任务类型(CPU/IO/混合)→ 估等待占比 → 定核心线程数 → 定有界队列容量 → 定拒绝策略与监控项(活跃线程、队列长度、拒绝次数、P99)。上线按指标回调。 **D. 线上定位序**:现象(CPU 高/RT 抖动/线程暴涨)→ `top -Hp` 找热点 → `jstack` 看 `BLOCKED` 与 `waiting to lock` → 查死锁 → 查线程池与队列指标 → 查 GC 停顿 → 回代码找临界区。死锁四条件中最实用的是统一加锁顺序 + tryLock 超时。 ## 五、我的阅读轨迹 | 项目 | 数据 | |---|---| | 进度 | 100% | | 累计阅读 | 19小时57分钟 | | 开始阅读 | 2017-04-14 | | 最近阅读 | 2024-05-23 | | 读完时间 | 2017-11-07 | **复习建议**:已读完,首读 2017 年、末次翻看 2024-05-23,属「读过但细节会漏」。排序复读:**第3章**最先(篇幅最大,happens-before 八条与 DCL 需反复推导);**第5章**次之(AQS 必须配源码读,只读文字会「看懂但写不出」);**第9、10章**结合现网配置复盘参数与拒绝策略。**可略读**:第7章与第1章(结论已内化)。**待核对**:书中 CHM 以 JDK 7 分段锁为主线,JDK 8+ 需查差异;虚拟线程未覆盖,属时代边界。 ## 六、行动清单 1. 用 `jstack` + `top -Hp` 做线程快照,标出 `BLOCKED`/`WAITING` 线程及锁持有者。 2. 把 `newFixedThreadPool`/`newCachedThreadPool` 改为显式 `ThreadPoolExecutor`,队列有界、写清拒绝策略。 3. 给线程池补活跃线程、队列长度、拒绝次数、P99 四项监控,接告警。 4. 复核每个 `volatile` 是否为一写多读或状态标志,检查 DCL 单例是否漏 volatile。 5. 修正吞掉 `InterruptedException` 的 catch,改为恢复标志或上抛。 6. 在每个 `ThreadLocal` 使用点的 finally 补 `remove()`,重点查线程池链路。 7. 手写基于 AQS 的不可重入锁,跑通两线程互斥以验证 state 与队列。 ## 七、延伸阅读 1. **《Java并发编程实战》(Brian Goetz 等)**——先读或并读。它讲「怎么设计正确的并发程序」(不变性、任务取消、可伸缩性),本书讲「JVM 与 JDK 怎么实现」。只读一本时,实践选它,源码选本书。 2. **《深入理解Java虚拟机》(周志明)**——放在本书第2、3章之后。本书只讲到支撑并发所需的 JVM 知识(对象头、内存屏障、锁优化),补齐类加载、GC、JIT 要靠它。 3. **《操作系统概念》**——不必通读,只看调度、同步与信号量、死锁、缓存一致性几节,把上下文切换、Monitor、屏障放回 OS 语境。 **顺序建议**:Goetz(建立正确性直觉)→ 本书(打通底层与源码)→ 虚拟机(补齐运行时)→ 操作系统(回填硬件层)。不必按序读完,但每读一本都回来交叉验证。