Java并发三剑客:synchronized、volatile与CAS底层原理与实战避坑指南

发布时间:2026/10/18 19:58:56
Java并发三剑客:synchronized、volatile与CAS底层原理与实战避坑指南 synchronized、volatile、CAS这三个关键字在Java并发编程里几乎是必考必问的内容。无论是校招还是社招面试官只要问到并发大概率就会从这三者切入考察对并发基础的理解深度。很多人能背出各自的定义但一旦问到“它们到底有什么区别”“什么场景该用谁”就含糊了。这篇文章我不打算像教科书一样罗列概念而是从实际开发的角度把三者的底层原理、使用场景、相互搭配以及面试中常见的问题和坑一次性讲透你可以直接把它当作一份并发编程的复习提纲和避坑指南。1. 三者定位并发编程中你必须拎清的三把武器先建立一个整体认知。在Java并发编程里我们最终要解决的就是两个核心问题多个线程之间的原子性和可见性。原子性解决的是“这个操作会不会被其他线程打断”可见性解决的是“一个线程修改了变量另一个线程能不能立刻看到”。传统的synchronized靠加锁同时解决了这两个问题但代价是线程可能被阻塞性能开销大。volatile和CAS则是为了在特定场景下用更轻量的方式解决其中部分问题。1.1 synchronized重量级锁但JVM已经帮你优化到“几乎无感”synchronized是Java语言层面对“管程”的原生支持简单理解就是一个互斥锁。线程进入synchronized代码块前必须先获得锁否则就阻塞等待。之所以说它是“重量级”的是因为在JDK 1.6以前synchronized底层直接依赖操作系统的互斥量实现。一旦锁竞争激烈线程就会从用户态切换到内核态被挂起这个切换开销非常大所以早期的synchronized性能远不如后来的显式锁。但JDK 1.6之后JVM做了大量锁优化加入了偏向锁、轻量级锁、锁消除、锁粗化等机制让synchronized在无竞争或低竞争场景下的开销变得非常小很多场景下甚至比ReentrantLock还快。这也是为什么很多现代Java代码里官方推荐优先使用synchronized。1.2 volatile轻量级可见性保证最容易被忽视的“新值通知”volatile是Java语言提供的最轻量的同步机制它所解决的是可见性问题它的核心作用有两个保证变量对所有线程的可见性以及禁止指令重排序。打个比方synchronized是一条有专人看守的专用通道通道内不允许外人进入volatile则更像是店铺门口挂了一个“本店改价”的公告牌所有顾客都能第一时间看到最新价格但买东西的时候该排队还是得排队。volatile不保证原子性这正是大量面试题的出处。很多人会把“volatile保证可见性”等同于“volatile保证线程安全”这是完全错误的理解。1.3 CAS无锁原子操作乐观派的代表CAS全称是Compare And Swap即“比较并交换”。它是一条CPU级别的原子指令做的事情是比较内存当前值与预期值是否相等如果相等则更新为新值整个过程不可被打断。synchronized是悲观锁思想反正我不知道会不会冲突先锁住再说CAS是乐观锁思想我假设一般情况下不会冲突先尝试执行更新如果发现值被改了就重试。CAS最典型的落地就是java.util.concurrent.atomic包下的AtomicInteger、AtomicLong、AtomicReference等原子类以及ConcurrentHashMap等并发容器内部的大量实现。2. 底层原理从字节码到CPU指令看看它们怎么工作的面试中如果只聊用法那只能算“背答案”。要真正打动面试官就要把原理讲清楚哪怕只是点出关键机制名也会明显拉开差距。2.1 synchronized的锁升级路径从偏向锁到重量级锁synchronized锁的底层实现是基于对象头Mark Word中的锁标志位。JVM根据竞争激烈程度把锁的状态分为四个级别无锁、偏向锁、轻量级锁、重量级锁并且只允许朝加重方向升级现在也支持一定条件下的降级。偏向锁是“最乐观”的阶段假设同一个线程会反复获取同一把锁于是锁记录中直接存储线程ID持有锁的线程再次进入时没有任何额外开销。一旦出现其他线程竞争偏向锁被撤销并升级为轻量级锁。轻量级锁通过自旋的方式尝试获取锁即线程不立即阻塞而是忙循环等待锁释放。JDK的spin默认开启且自适应JVM会根据历史自旋成功率动态调整自旋次数以避免空转浪费CPU。如果自旋仍然失败则膨胀为重量级锁线程挂起并进入操作系统内核的同步队列这是最昂贵的状态。在面试中只要能讲出“偏向锁-轻量级锁-重量级锁”的升级路径再补充一句“自旋是自适应的JDK会根据上次自旋结果动态调整”就能体现你是真正看过原理而不是死记硬背。2.2 volatile的内存屏障Lock前缀指令和重排序volatile的底层实现靠的是内存屏障Memory Barrier。编译器和CPU为了提升执行效率会把指令重新排序这在单线程下没有影响但在多线程下可能导致语义变化。volatile通过在读写操作前后插入内存屏障禁止特定类型的重排序。具体机制可以这样理解对于volatile写操作JVM会插入StoreStore屏障和StoreLoad屏障对于volatile读操作会插入LoadLoad屏障和LoadStore屏障。这些屏障同时保证了可见性——写线程修改的值必须刷入主内存读线程直接越过CPU缓存去主内存读取。在x86平台上内存屏障的实际实现就是一条lock前缀指令它同时具备两个效果一是将当前CPU缓存行写回主内存二是使其他CPU缓存行失效。这就是为什么volatile能保证“一个线程写其他线程立刻看到”的根本原因。需要注意的是volatile只能限制编译器和CPU层面的重排序它无法限制多个线程并发读写造成的“中间值”问题。也就是说volatile声明的变量多个线程同时进行自增操作依然会丢值。2.3 CAS的硬件级支持Unsafe.compareAndSwapXXXCAS在Java中的入口是sun.misc.Unsafe类它提供了一组compareAndSwapInt、compareAndSwapLong、compareAndSwapObject等native方法。这些方法最终依赖CPU的原子指令比如x86平台的CMPXCHG指令。关键点在于“CPU指令是原子的”也就是说CAS这个比较加交换的动作在CPU层面不会被中断。即便多个线程同时发起CAS硬件层面也会顺序执行因此不需要加锁线程执行CAS失败后可以选择自旋重试或者直接放弃。AtomicInteger内部就维护了一个volatile修饰的int value执行incrementAndGet()时底层就是一个do-while循环不断调用compareAndSwapInt直到成功为止。这里的volatile保证的是读取到的变量始终是最新的CAS负责的是更新的原子性。这两者搭配起来才形成了完整的无锁安全自增。3. 区别与应用场景面试题和实战选择的统一答案很多面试题问“synchronized、volatile、CAS区别”之类的题目考察的其实就是你对这三种手段边界和成本的把握。这里我用一个表格来总结再展开讲实战选择逻辑。3.1 一个表格看懂三者的核心区别维度synchronizedvolatileCAS解决的核心问题原子性 可见性可见性 禁止重排序原子性是否阻塞竞争时会阻塞永不阻塞失败时自旋重试不阻塞粒度方法级 / 代码块级变量级变量级原子操作是否可重入可重入不适用非锁不适用非锁典型实现锁升级、管程内存屏障Unsafe CPU指令性能开销重但经过优化最轻无竞争时很高竞争激烈时可能自旋浪费CPU适用位置临界区、需要复合操作的场景状态位标记、读写不依赖旧值场景计数器、原子类、并发容器这个表格可以作为面试时的答题骨架。面试官如果问“volatile和synchronized的区别”先答核心目的——synchronized解决原子性和可见性volatile只解决可见性再补上是否阻塞、粒度大小、性能开销三个维度这样的回答基本就能覆盖到所有考点。3.2 经典组合volatile CAS并发容器的基石单独使用volatile或CAS都有各自的局限volatile不保证原子性CAS在高并发场景下如果没有volatile配合读到的值可能是过期的。所以实际中最常见的是两者搭配使用。AtomicInteger就是一个最典型的例子。它内部有一个volatile int value字段保证了所有线程对value的可见性而getAndIncrement()等方法则通过CAS无锁更新value。这样既保证了读到的值永远是最新的又保证了自增操作不会丢值同时还避免了锁带来的阻塞开销。ConcurrentHashMap在JDK 8的源码里也大量使用了volatile和CAS的组合Node数组用volatile修饰以保证数组扩展后的可见性槽点初始化用CAS避免多线程竞争。这就是为什么它对高并发场景的吞吐能力远强于JDK 7版本的实现。如果你能在项目里举出这样的例子说明你是“用过”这组技术的而不是停留在面试记忆层面。3.3 场景选型从状态标志到并发计数器先说volatile的典型场景。最经典的是多线程中的“状态标志位”一个线程在运行另一个线程根据flag决定是否停止它这里用volatile修饰flag就足够了因为读和写之间不存在依赖旧值的复合操作。public class Worker { private volatile boolean running true; public void stop() { running false; } public void run() { while (running) { // 执行任务 } } }再来看synchronized的典型场景。如果多个线程要同时修改同一个业务对象而修改过程是“读-改-写”三步操作比如转账时检查余额再扣款就必须用synchronized保证这个复合操作的整体原子性否则会出现并发下超额扣款的问题。public synchronized boolean transfer(Account target, int amount) { if (this.balance amount) { return false; } this.balance - amount; target.balance amount; return true; }CAS则适合读多写少、但写操作本身非常轻量的场景典型就是并发计数器。如果每读取一次都要加锁代价太大如果用AtomicInteger配合CAS无锁无阻塞性能好得多。比如一个接口的访问量统计直接用AtomicLong就够了。private final AtomicLong requestCount new AtomicLong(0); public void onRequest() { requestCount.incrementAndGet(); }这里要特别提醒一点CAS不适合用在操作特别复杂、耗时较长的场景。一旦竞争激烈失败的线程会不停自旋重试大量空转反而比锁还慢这种情况应当考虑分段思想如LongAdder或直接使用synchronized。4. 面试必背题目与答题思路下面把面试中最高频的几类问题单独列出来给出建议的答题思路。这些问题看似零散其实都是围绕前面讲到的原理和区别展开的。4.1 高频面试题“volatile能保证原子性吗”这类问题正确的回答思路应该是先给出明确结论——不能再解释原因——volatile只保证可见性和禁止指令重排它不包含任何锁机制无法阻止多线程同时执行“读-改-写”操作。然后可以举反例加以说明。比如多个线程同时对volatile int变量执行count这个操作实际是拆分成“读取count、计算count1、写回count”三步的线程A和线程B可能同时读到同一个旧值各自加1写回最终只增加了1丢了一次更新。补充一点进阶表述如果需要保证原子性要么改成AtomicIntegerCAS实现要么用synchronized给自增操作加锁。这样回答逻辑链完整面试官很难再追问出破绽。4.2 高频面试题ABA问题与解决方案CAS有个经典缺陷叫ABA问题线程A读取到变量值X线程B将它改为Y后又改回X线程A执行CAS时发现值仍是X认为没有变化于是操作成功但实际上变量已经被其他线程动过了。最典型的场景就是“链表头部指针更新”如果链表头部从Node1变为Node2又变回Node1CAS无法感知这次变化而基于指针进行的安全判定可能就会失效。解决办法是引入版本号。Java中的AtomicStampedReference就提供了带版本号的CAS操作其中stamp充当版本号每次更新都附带递增一个stampCAS时同时校验值和版本号从而避免ABA问题。实际上只需要说清楚“用版本号或者时间戳来标识每次修改”即可因为不同语言实现方式大同小异。4.3 进阶话题锁消除、锁粗化和JIT优化这一块如果能在面试中主动讲出来会是很大的加分项。JVM在执行热点代码时会发生JIT即时编译优化期间可能对synchronized做额外优化。锁消除是指JVM基于逃逸分析发现某个对象根本没有被共享锁根本不必要于是直接去掉锁。比如在方法内部new一个局部对象并加锁但这个对象根本没被其他线程看到锁就是多余的JVM会直接剔除。锁粗化则相反它发现代码中一连串循环都在对同一把锁加解锁比如for循环里每次迭代都加一次锁这时JVM会把锁的范围扩大到整个循环减少反复加解锁的开销。能用几句话讲出这两个机制面试官基本就会认定你对并发底层是有系统理解的而不是背了几个API。4.4 高频面试题为什么双重检查锁单例要加volatile这个问题可能是全Java并发面试中出现频率最高的一种变体。标准的双重检查锁单例代码是public class Singleton { private static volatile Singleton instance null; private Singleton() {} public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }很多人知道要写volatile但说不清为什么。核心原因是new Singleton()这一步在底层不是原子的它被拆分成“分配内存、初始化对象、将引用指向内存地址”三个步骤经过指令重排序后可能先完成引用赋值、再执行对象初始化。这时候如果另一个线程进入了getInstance()读到instance非空直接返回使用到的却是一个尚未初始化完成的对象程序就会发生不可预料的错误。volatile禁止了这种重排序保证对象一定先完成初始化再被引用指向。建议回答时先抛出“创建对象不是一个原子操作”这个关键点再解释重排序风险这样基本就能把分拿满因为大多数人只会背代码而讲不出这个原因。5. 实操中踩过的坑与避坑指南原理讲透了还是要落到实际开发。我把自己在项目里踩过、帮别人排查过的几个典型坑整理出来这些坑都是并发编程“看起来对、跑起来就错”的经典案例。5.1 坑一把volatile当计数器用线上数据丢失有次接到一个线上排查任务某个统计指标在高峰期总是少计数。代码里用的是volatile long counter然后在多线程里执行counter。表面上看没错跑到线上就丢数据而且难以复现。排查思路很清楚三步走先确认确实是并发丢数据再确认counter不是原子操作最后替换为AtomicLong对比前后结果。最终替换为LongAdder之后统计值恢复准确而且因为LongAdder做了分段减锁竞争性能反而更好。这里重要的一点经验是凡是涉及“读-改-写”的共享变量自增场景永远不要用volatile单独扛。5.2 坑二synchronized包裹整个大方法把接口拖死另一个常见问题是同步范围过大导致并发性能急剧下降。有人在业务方法最外层直接加上synchronized把所有线程排队去执行一长串业务逻辑结果就是接口吞吐量崩塌。这种情况建议遵循“同步块尽量小”的原则只把真正需要原子性保护的临界区代码包进去。比如真正需要保护的是“查询余额-扣减余额”两步那就只锁这几行把网络IO、日志记录等放到锁外执行性能能提升数倍。还需要注意锁的粒度平衡锁太小会增加反复加解锁开销锁太大会增加排队等待时间需要结合业务实际做取舍而不是把synchronized一加了事。5.3 坑三双重检查锁单例漏写volatile偶发NullPointerException这个坑在实操中很经典。有人写双重检查锁单例时觉得“synchronized已经够安全了”于是就没加volatile结果线上偶发NPE排查极其困难。我在排查这类故障时总结了一个经验如果代码里出现了双重检查锁模式并且变量没有volatile修饰那基本可以断定就是它引发的。这个经验帮助我在一次线上事故里快速定位了问题——最终加上volatile修饰NPE就再也没有出现过。这就是我在全文中反复强调的那句话知识不落地就是纸面功夫。你以为synchronized已经解决了所有并发问题但编译器的指令重排序可能在底层悄悄埋雷而volatile正是拔掉这颗雷的那道屏障。5.4 坑四盲目使用CAS导致CPU飙升CAS虽然无锁但竞争激烈时线程会持续自旋长时间拿不到锁就长时间空转。不少人用过AtomicBoolean加自旋锁在线程数量较大、临界区耗时长的时候CPU会飙升到爆炸。一个典型的错误写法是用一个AtomicBoolean做自旋锁临界区里有较重的IO操作。private final AtomicBoolean locked new AtomicBoolean(false); public void execute() { while (!locked.compareAndSet(false, true)) { // 自旋等待 } try { // 这里做比较重的IO或数据库操作 } finally { locked.set(false); } }临界区越重竞争时长越长其他线程自旋越夸张CPU占用越高。这种场景必须改用synchronized至少让线程在竞争时真正挂起休眠而不是空转。如果确实追求性能可以考虑用带退避的策略或者干脆用Java自带的ReentrantLock。6. 做个简单总结但这些经验你一定要记进代码如果只让我留下一句话那就是synchronized适合需要原子性和可见性且临界区较重的场景volatile适合只关注状态可读性、不涉及读-改-写复合操作的场景CAS适合高频读取、轻量更新且竞争不激烈的场景三者不是替代关系而是互补关系。最后分享一个我个人比较受用的实践习惯每遇到一个并发问题总是先问自己三个问题——“这个操作是否需要原子性是否需要可见性冲突概率有多高”答案出来之后选型自然就清楚了。下次写并发代码时你也可以先按这个思路过一遍再决定用synchronized、volatile还是CAS很多线上事故其实都可以在这种追问中提前规避掉。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询