《Java 并发编程的艺术》个人总结

为什么读这本书

工作中写并发代码的机会不少——线程池、缓存、异步处理——但遇到诡异的可见性问题或死锁时,往往只能靠加 synchronized 碰运气。这本书系统地梳理了 Java 内存模型和并发工具的设计原理,读完确实对调试并发问题有了更清晰的思路。

核心知识点梳理

1. 并发编程的挑战

书中开篇说的三个核心问题:

  • 上下文切换:线程多了,CPU 花在切换上的时间比干活还多
  • 死锁:互相等对方的锁
  • 资源限制:硬件瓶颈不解决,加再多线程也没用

经验上,线程数 ≈ CPU 核数 × 2(取决于 IO 密集还是计算密集) 是一个比较可靠的起点。

2. Java 内存模型(JMM)

这是全书最重要的一章。JMM 控制的是一个线程对共享变量的写入何时对另一个线程可见。核心概念:

  • happens-before 规则:程序顺序、volatile、锁、传递性等
  • volatile:保证可见性和禁止指令重排序,但不保证原子性
  • 锁:synchronized 和 ReentrantLock 同时保证原子性和可见性

并发编程中大部分诡异 bug 的根因都是对 JMM 的理解不到位——以为一个线程写完了另一个线程就一定能读到。

3. 并发基础组件

组件核心作用使用场景
volatile轻量级可见性保证状态标识位
synchronized互斥 + 可见性临界区保护
ReentrantLock可中断/超时/公平锁需要灵活锁控制的场景
AtomicInteger/Long/ReferenceCAS 无锁原子操作计数器、状态机
CountDownLatch等待多个线程完成并行任务汇总
CyclicBarrier多个线程互相等待分阶段并行计算
Semaphore流量控制限流、资源池

4. 线程池

ThreadPoolExecutor 的核心参数关系:

  • corePoolSize/ maximumPoolSize:核心线程和最大线程数
  • keepAliveTime:非核心线程的空闲存活时间
  • workQueue:任务队列满了才会创建新线程(从核心数涨到最大数)

实践中常见的问题是队列设得太大——线程池永远只用核心线程,失去了弹性伸缩的意义。另外 Executors.newCachedThreadPool() 要慎用,它允许无限创建线程。

5. 并发集合

  • ConcurrentHashMap:分段锁(JDK7)→ CAS + synchronized(JDK8),读不加锁
  • CopyOnWriteArrayList:写时复制,适合读多写少
  • BlockingQueue:生产者-消费者模式的基础

6. 锁优化

JVM 对 synchronized 做了大量优化:

  • 偏向锁:同一个线程反复获取锁,直接放行
  • 轻量级锁:多线程交替执行,用 CAS 代替互斥
  • 重量级锁:真正的线程阻塞

这也是为什么现在不需要一味追求 ReentrantLock——JVM 的 synchronized 优化已经足够好,除非你需要锁中断或超时。

一点个人感悟

读完这本书最大的收获不是记住了多少 API,而是理解了 JMM 的 happens-before 规则后,排查并发问题有了一个系统的思维框架。不再是把 volatile、synchronized、Lock 当成"可能管用"的咒语到处乱加,而是能根据问题的性质(可见性?原子性?有序性?)针对性地选择工具。

建议读完这本书后,再配合《Java Concurrency in Practice》一起看,两本书互为补充。