# 《Java并发编程之美》知识点全解 > AI生成|2026年10月28日|codebuddy|原书:翟陆续 薛宾田《Java并发编程之美》,电子工业出版社,2018-10-01 > **我的阅读**:进度 100%|累计 8小时21分钟|开始 2019-08-24|最近 2024-05-12|读完 2019-09-02 **一句话总结**:把 Java 并发从“会写 synchronized 和线程池”推进到“理解 JMM、CAS、AQS 与 JDK 并发包源码,并能定位业务中的并发 bug”。 **适合谁 / 前置知识**:适合已掌握 Java 语法、写过少量多线程代码,想从“会用 API”进阶到“懂源码、能排错”的后端开发者。需要了解类、接口、泛型、集合与异常,最好用过 synchronized 和线程池。不适合零基础或只想查 API 的读者;本书源码密度高,应按基础篇、高级篇、实践篇递进阅读。 ## 一、全书地图 | 部分 | 章节 | 主要内容 | 逻辑位置 | |---|---|---|---| | 基础篇 | 第1章 | 线程创建、wait/notify、join/sleep/yield、中断、死锁、守护线程、ThreadLocal | 线程怎么跑、怎么停、怎么协作 | | 基础篇 | 第2章 | 线程安全、可见性、synchronized、volatile、原子操作、CAS、Unsafe、重排序、伪共享、锁概述 | 共享内存为什么会出错 | | 高级篇 | 第3章 | Random 的局限与 ThreadLocalRandom | 减少竞争 | | 高级篇 | 第4章 | 原子变量、LongAdder、LongAccumulator | 无锁计数与高并发累加 | | 高级篇 | 第5章 | 并发 List(写时复制思路) | 读多写少 | | 高级篇 | 第6章 | AQS、ReentrantLock、ReentrantReadWriteLock、StampedLock | 锁的骨架与实现 | | 高级篇 | 第7章 | ConcurrentLinkedQueue、LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue、DelayQueue | 并发队列选型与源码 | | 高级篇 | 第8章 | ThreadPoolExecutor 类图与源码 | 任务执行与资源治理 | | 高级篇 | 第9章 | ScheduledThreadPoolExecutor | 延时与周期任务 | | 高级篇 | 第10章 | CountDownLatch、CyclicBarrier、Semaphore 等同步器 | 线程间协作 | | 实践篇 | 第11章 | Tomcat NioEndPoint、ConcurrentHashMap、SimpleDateFormat、Timer、深复制、线程命名、shutdown、FutureTask、ThreadLocal 泄漏 | 原理落成业务规约 | 逻辑关系:基础篇建立“线程生命周期 + 共享内存三性”;高级篇按“减少竞争—原子操作—容器—锁—队列—执行器—协作器”展开;实践篇把源码知识回收为上线检查项。AQS 依赖 CAS/volatile,线程池依赖阻塞队列,实践篇的坑大多能回溯到前两篇,不建议跳读。 ## 二、核心知识点 **1. 线程创建、启动与终止** - **是什么**:`Thread` 是执行载体,`Runnable/Callable` 是任务;`start()` 才创建新线程。 - **为什么**:`run()` 只是普通方法调用;线程终止后不能复活。 - **怎么用**:有返回值用 `Callable + Future`,`get()` 设超时。 - **误区**:直接调 `run()` 不会并发;重复 `start()` 抛 `IllegalThreadStateException`。 **2. wait/notify/notifyAll** - **是什么**:Object 的监视器方法,调用前必须持有同一把锁;`wait()` 释放锁并进入等待集,唤醒后还要重新竞争锁。 - **为什么**:用通知替代忙等;通知可能丢失,条件可能被其他线程改变。 - **怎么用**:`synchronized(lock){ while(!条件) lock.wait(); 业务; lock.notifyAll(); }`。 - **误区**:用 `if` 代替 `while`,遇虚假唤醒就错;无锁调用抛 `IllegalMonitorStateException`;只 `notify()` 可能唤醒错对象。 **3. join/sleep/yield/interrupt** - **是什么**:`join` 等目标线程结束;`sleep` 休眠但不释放监视器锁;`yield` 只是让出 CPU 的提示;`interrupt` 设置标志并唤醒阻塞。 - **为什么**:控制时序与取消,避免无限等待和资源悬挂。 - **怎么用**:等待加超时;捕获 `InterruptedException` 后恢复中断或退出;`Future.cancel(true)` 发送中断。 - **误区**:以为 `interrupt()` 强制杀线程;吞掉中断;误以为 `sleep()` 会释放锁;使用已废弃的 `stop()`。 **4. 死锁、守护线程与上下文切换** - **是什么**:死锁是线程互相持有并等待对方锁;守护线程不阻止 JVM 退出;上下文切换是保存和恢复执行现场。 - **为什么**:死锁让服务挂起;线程越多,切换开销越大,吞吐可能下降。 - **怎么用**:固定加锁顺序、`tryLock` 超时、缩小锁范围;用 `jstack` 查 BLOCKED 与锁持有链;守护线程只做辅助任务。 - **误区**:嵌套锁顺序不一致;把关键业务放守护线程;盲目加线程解决性能问题。 **5. ThreadLocal** - **是什么**:为每个线程保存独立副本,值在 Thread 的 `ThreadLocalMap` 中,key 是弱引用、value 是强引用。 - **为什么**:隔离非线程安全对象(如 `SimpleDateFormat`);线程池复用线程会残留数据。 - **怎么用**:`static final ThreadLocal`;`initialValue/withInitial`;`finally` 中 `remove()`。 - **误区**:不 `remove` 导致泄漏和串数据;当全局缓存;以为子线程自动继承——需 `InheritableThreadLocal`,线程池场景仍要谨慎。 **6. JMM、synchronized 与 volatile** - **是什么**:JMM 规定可见性、原子性、有序性;`synchronized` 提供互斥、可见性和可重入;`volatile` 提供可见性并禁止特定重排序,但不保证复合原子性。 - **为什么**:CPU 缓存和编译器优化会让写不可见,或让对象“半初始化”就被看到。 - **怎么用**:不可变对象 + `final` 安全发布;状态标志用 `volatile`;复合操作加锁或原子类;DCL 给实例字段加 `volatile`。 - **误区**:加 `volatile` 就以为线程安全;锁不同对象;锁 `String/Integer` 等可能复用的对象;忽略 `this` 逸出。 **7. CAS、Unsafe 与原子类** - **是什么**:CAS 是“比较并交换”,依赖 CPU 原子指令;`Unsafe` 暴露 CAS、`park/unpark`;原子类封装 CAS。 - **为什么**:无锁减少阻塞;高并发下 `AtomicLong` 自旋冲突大,`LongAdder` 用分段 Cell 降低热点。 - **怎么用**:普通计数用 `AtomicInteger/AtomicLong`;高并发累加用 `LongAdder`;自定义规则用 `LongAccumulator`。 - **误区**:忽视 ABA(必要时用 `AtomicStampedReference`);CAS 自旋耗 CPU;把 `LongAdder.sum()` 当强一致快照;业务代码滥用 `Unsafe`。 **8. 指令重排序与伪共享** - **是什么**:重排序来自编译器、CPU 和内存系统;伪共享是不同变量落在同一缓存行,被多核反复失效。 - **为什么**:重排序破坏直觉;伪共享让“看似无共享”的代码性能骤降。 - **怎么用**:用 `volatile/synchronized/final` 建立有序性;高频独立变量做缓存行填充或用 `@Contended`;用压测验证。 - **误区**:DCL 漏写 `volatile`;为所有字段盲目填充,浪费内存;不测量就断言存在伪共享。 **9. AQS 与锁家族** - **是什么**:AQS 用 `volatile int state` 表示同步状态,配合 FIFO 等待队列和 `LockSupport.park/unpark`;子类实现 `tryAcquire/tryRelease` 等模板方法。 - **为什么**:`ReentrantLock`、`ReentrantReadWriteLock`、`Semaphore`、`CountDownLatch` 都建立在同一套排队与阻塞机制上。 - **怎么用**:理解独占/共享、公平/非公平、`Condition` 队列;加锁后必须 `try/finally` 解锁;`StampedLock` 乐观读后校验 stamp。 - **误区**:忘记 `unlock()`;读写锁试图升级;`StampedLock` 不可重入,锁模式与 stamp 不能混用。 **10. 并发容器与队列** - **是什么**:并发 List 用写时复制;`ConcurrentLinkedQueue` 用 CAS 链接节点;`LinkedBlockingQueue` 双锁;`ArrayBlockingQueue` 单锁双条件;`PriorityBlockingQueue` 用堆;`DelayQueue` 按延迟出队。 - **为什么**:不同结构在吞吐、内存、阻塞、排序和延迟上取舍不同,选错会 OOM 或失去并发优势。 - **怎么用**:读多写少用写时复制 List;生产者-消费者优先 `BlockingQueue`;有界用 `ArrayBlockingQueue/LinkedBlockingQueue`;优先级用 `PriorityBlockingQueue`;延时任务用 `DelayQueue`。 - **误区**:写时复制 List 频繁写导致内存压力;迭代器只是快照;`LinkedBlockingQueue` 默认容量近无界;`PriorityBlockingQueue` 不是 FIFO;`ConcurrentHashMap` 的“先查后改”仍需原子复合方法。 **11. 线程池、调度器与同步器** - **是什么**:`ThreadPoolExecutor` 由核心线程、任务队列、最大线程和拒绝策略组成;`ScheduledThreadPoolExecutor` 用延时队列;`CountDownLatch/CyclicBarrier/Semaphore` 负责协作。 - **为什么**:池化控制资源、复用线程;同步器把“等事件、等会合、限并发”从手写 wait/notify 中抽出来。 - **怎么用**:有界队列 + 业务命名 `ThreadFactory` + 明确拒绝策略;`shutdown + awaitTermination`;启动门用 Latch,分阶段用 Barrier,限流用 Semaphore。 - **误区**:`Executors.newFixedThreadPool` 使用无界队列;以为最大线程先于队列扩容;`submit` 后不看 `Future` 异常;忘记关闭;周期任务抛异常后不再执行;Latch 不能重置。 **12. 实践篇的高频坑** - **是什么**:`SimpleDateFormat` 非线程安全;`Timer` 单线程且任务异常会终止定时器;复用参数可能被下游修改;线程和池无名难排查;池不关闭会阻止退出;`FutureTask` 异常可能被吞;ThreadLocal 使用不当会泄漏。 - **为什么**:这些不是 API 不会用,而是边界条件与生命周期管理缺失。 - **怎么用**:用 `DateTimeFormatter` 或 ThreadLocal 包装日期格式;用 `ScheduledExecutorService` 替代 `Timer`;对外返回前深复制;线程池命名并关闭;`Future.get()` 处理 `ExecutionException`;ThreadLocal 在 `finally` 清理。 - **误区**:开发环境跑通就上线;把线程池当静态变量永不关闭;复制引用就当深复制。 ## 三、关键概念速查表 | 概念 | 一句话解释 | 用在哪儿 | |---|---|---| | happens-before | 跨线程操作的可见性先后约束 | 判断无锁代码是否可见 | | volatile | 可见性 + 禁止特定重排序,不保证复合原子性 | 状态标志、DCL | | synchronized | 监视器互斥、可重入、可见性 | 保护不变式 | | CAS | 比较并交换的原子指令 | 原子类、无锁队列 | | ABA | 值改回原值导致 CAS 误判 | `AtomicStampedReference` | | AQS | state + FIFO 队列 + park/unpark | 锁与同步器 | | ThreadLocal | 每线程独立副本 | 隔离非线程安全对象 | | 写时复制 | 写时复制新数组,读旧快照 | 读多写少 List | | CLQ | CAS 非阻塞链表队列 | 高并发无界队列 | | BlockingQueue | 支持阻塞 put/take 的队列 | 生产者-消费者 | | ThreadPoolExecutor | 核心/最大线程 + 队列 + 拒绝策略 | 任务执行 | | CountDownLatch | 一次性等待计数归零 | 启动门、等待完成 | | CyclicBarrier | 可复用屏障,等齐后继续 | 分阶段并行 | | Semaphore | 许可数量控制并发 | 限流、资源池 | | StampedLock | 乐观读 + 悲观读写锁 | 读多写少 | | 伪共享 | 同一缓存行被多核争用 | 高性能计数器 | ## 四、可复用的方法/框架 **判断四问** 1. 有没有共享可变状态?没有就不加锁;有则继续。 2. 是只读还是写?只读用 `final/不可变`;写用原子类或锁。 3. 需要互斥还是协作?互斥用 `synchronized/Lock`;协作用 `wait/Condition` 或同步器。 4. 需要管理任务还是限流?任务用线程池;限流用 `Semaphore` 或有界队列。 **编码模板** - 锁:`lock(); try { ... } finally { unlock(); }`。 - 等待:`while (!条件) condition.await();`,醒来后复检条件。 - ThreadLocal:`try { ... } finally { threadLocal.remove(); }`。 - 线程池:业务命名 + 有界队列 + 拒绝策略 + 关闭钩子。 **验证**:先写最小复现,再用 `jstack`、线程 dump、压测观察 BLOCKED/WAITING 与死锁报告;不要只看日志,也不要把“没有报错”当成线程安全。 ## 五、我的阅读轨迹 | 项目 | 数据 | |---|---| | 进度 | 100% | | 累计阅读 | 8小时21分钟 | | 开始阅读 | 2019-08-24 | | 最近阅读 | 2024-05-12 | | 读完时间 | 2019-09-02 | 已读完,无未读章节。建议按主题复习:第一轮复盘第6章 AQS、第7章队列、第8章线程池,画一张“任务从 submit 到执行/拒绝”的状态图;第二轮对照第11章,审计项目中的线程池、ThreadLocal、日期格式化和定时任务;第3—5章可在选型时回查。 ## 六、行动清单 1. 用 `jstack` 复现并定位一次死锁,记录锁持有链。 2. 手写 wait/notify 版生产者-消费者,再改用 `BlockingQueue` 对比。 3. 把项目中的一个线程池改为有界队列、业务命名和明确拒绝策略。 4. 用 ThreadLocal 封装 `SimpleDateFormat`,并在 `finally` 中 `remove()`。 5. 用 `AtomicLong` 与 `LongAdder` 做同一计数场景的压测对比。 6. 阅读 AQS 的 `acquire/release` 与 `Condition` 入队出队源码,画出状态流转。 7. 审计所有 `Future.get()` 和周期任务,补齐异常处理、超时与监控。 ## 七、延伸阅读 1. **《Java多线程编程核心技术》**:先读,补齐线程 API 与基础案例,降低本书源码阅读门槛。 2. **《Java并发编程实战》**:与本书并行或之后读,补并发设计原则与最佳实践;本书补 JDK 源码细节。 3. **《深入理解Java虚拟机》**:读完本书后读,深入 JMM、对象头、锁实现与 GC,解释并发机制的底层“为什么”。