第3章 并发编程基础与线程安全:volatile、synchronized、CAS、AQS

本章定位 大厂后端岗并发方向的高频入口题,考察候选人是否真正理解Java内存模型(JMM)、锁机制与无锁编程的核心差异。回答时应先讲原理,再落代码,最后用场景验证。

回答框架图

面对任何并发原理问题,建议按以下“四层递进”框架组织答案,既能体现深度又便于面试官抓取重点:

并发原理回答框架 原理本质 可见性/原子性/有序性 内存语义 happens-before / 屏障 应用场景 状态标识/互斥/无锁 源码证明 Unsafe / objectMonitor 对比优劣 synchronized vs Lock vs CAS
💡 面试加分技巧 回答时主动引入JMM规范(工作内存 vs 主内存),并说出对应的字节码指令或源码位置(如monitorenter、Unsafe_CompareAndSetInt),能明显拉开与普通候选人的差距。

🎯 面试问题 1:volatile 如何保证可见性和禁止指令重排序?

面试官考察点

  • 是否理解JMM中主内存与工作内存的交互
  • 对内存屏障(Memory Barrier)的认知程度
  • 能否区分volatile与synchronized在可见性上的差异

回答框架

  1. 定义本质:volatile是轻量级同步机制,保证可见性、禁止重排序,但不保证原子性。
  2. 可见性原理:对volatile变量写操作会立即刷新到主内存,读操作从主内存重新读取;底层通过Lock前缀指令触发缓存一致性协议(MESI)。
  3. 禁止重排序:插入内存屏障(LoadLoad、StoreStore等),阻止编译器和CPU重排。
  4. 适用场景:状态标志位(如done变量)、DCL单例双重检查。

📝 简短示例回答

volatile基于JMM的happens-before规则:写操作先于后续读。底层插入StoreStore屏障(禁止写写重排)和StoreLoad屏障(强制刷新主存),读时插入LoadLoad屏障(禁止读写重排)。典型场景如DCL单例中的instance变量,配合synchronized防止半初始化对象被读取。

可能追问

  • volatile能替代synchronized吗?为什么?
  • 64位变量(long/double)在32位JVM上使用volatile是否有问题?
  • 你们项目里哪里用到了volatile?
⚠️ 避坑提醒 不要回答“volatile保证原子性”!本质仅保证单次读/写是原子的,但复合操作(如 count++)仍需加锁。面试官经常用i++陷阱连环问。

🎯 面试问题 2:synchronized 底层实现与锁升级过程

面试官考察点

  • 是否了解对象头Mark Word的结构与锁状态
  • JDK 1.6锁优化(偏向锁→轻量级锁→重量级锁)的具体过程
  • 对线程阻塞和唤醒机制的理解(如park/unpark)

回答框架

  1. 字节码层面:通过monitorenter/monitorexit指令实现,JVM锁对象关联ObjectMonitor。
  2. 对象头存储:Mark Word中低2位表示锁标志:01无锁/偏向锁,00轻量级锁,10重量级锁。
  3. 锁升级流程:无锁→偏向锁(单线程CAS设线程ID)→轻量级锁(CAS替换锁记录)→重量级锁(挂入EntryList)。
  4. 优化时机:自旋次数、锁粗化、锁消除等编译器优化。

📝 简短示例回答

synchronized在字节码层用monitorenter/exit控制临界区。对象头Mark Word随竞争升级:假设线程A访问,先尝试偏向锁(CAS设线程ID);若有线程B竞争,撤销偏向升级为轻量级锁(自旋CAS);自旋超过阈值或CPU资源敏感,膨胀为重量级锁(Monitor阻塞队列)。

可能追问

  • 偏向锁在JDK 15中被默认禁用,为什么?
  • synchronized和ReentrantLock的区别?你选型的依据是什么?
  • 锁升级过程中Java线程状态如何变化?
⚠️ 避坑提醒 不要只背“锁升级”名词,要说明每个阶段的线程行为(如轻量级锁失败后膨胀)。面试官还常问:synchronized是非公平锁吗?是,因为线程挂起后唤醒顺序不保证。

🎯 面试问题 3:CAS 原理与 AQS 队列同步器设计

面试官考察点

  • 是否理解CAS的硬件原子性保证(CMPXCHG指令)
  • ABA问题及解决方案
  • AQS中CLH队列、state共享变量、独占/共享模式的抽象

回答框架

  1. CAS定义:CompareAndSwap,CPU原语,比较期望值与内存值相等则交换新值。
  2. Java实现:Unsafe类提供native方法,配合volatile变量保证可见性。
  3. ABA问题:A→B→A场景,使用AtomicStampedReference加版本号解决。
  4. AQS结构:维护volatile int state + FIFO双向CLH队列,通过CAS更新state,模板方法(tryAcquire/tryRelease等)。

📝 简短示例回答

CAS通过Unsafe.compareAndSwapInt调用CPU的CMPXCHG指令完成原子更新。AQS基于此设计同步器:内部state用volatile修饰,CAS修改;未获取锁的线程封装成Node入队,通过自旋+park阻塞等待。ReentrantLock、CountDownLatch等均复用该模板。

可能追问

  • CAS的自旋会带来什么问题?如何优化(如自旋锁自适应)?
  • 请手写一个简易的AQS自定义锁
  • AQS中独占锁与共享锁在队列管理上的差异
⚠️ 避坑提醒 不要混淆CAS与乐观锁的概念。CAS是机制,乐观锁是思想。回答AQS时建议画出CLH队列图示(本题可口头描述),重点说“入队前先CAS尝试一次”的优化。另外ABA问题必须提到版本号,只答“加版本号”不够,要说出AtomicStampedReference。
📚 章节小结 大厂面试中并发基础是“必问且拉分”的环节。掌握volatile的内存屏障、synchronized的锁升级、CAS的ABA处理以及AQS的队列模板,可以同时应对原理、源码、场景三类问题。建议结合《Java并发编程的艺术》第2~4章巩固。下一章将深入剖析线程池核心参数与拒接策略。