尧图精选

Java并发基础:volatile的可见性、有序性与内存屏障原理

🕒 发布时间:2026/10/2 10:18:14 📁 来源:尧图网络
聊个面试高频词volatile。只要面 Java 并发编程十个面试官八个会问它剩下的两个会把它藏在双重检查锁、单例模式或者其他并发组件里拐着弯考。很多初学者把它背成“保证可见性、禁止指令重排、不保证原子性”这三句话就上考场结果面试官追问一句“为什么能保证可见性”“内存屏障到底是什么”直接就卡住了。这篇文章我把 volatile 从面试角度和技术角度一起拆开讲包括它到底解决了什么问题、底层怎么实现的、哪些场景能用哪些场景不能用以及面试官问这个问题时心里真正的考察点。全程会用代码、类比和踩坑记录来讲不管是准备面试还是想真正把并发基础打牢都值得读完。1. 为什么面试官总爱问 volatile1.1 它是检验并发功底的试金石先想一个事Java 并发编程的知识点那么多synchronized、Lock、CAS、线程池、AQS哪个都比 volatile 内容多为什么面试官偏偏拿它当敲门砖因为 volatile 恰好踩在并发编程的三个核心问题上可见性、有序性、原子性。这三个问题不搞清楚后面学什么并发工具都是浮沙筑台。面试官问 volatile表面上考一个关键字实际上考的是你对 Java 内存模型JMM的理解深度。一个候选人如果能从 volatile 讲到 JMM、讲到 CPU 缓存架构、讲到指令重排、讲到内存屏障那说明他是真理解并发编程底层逻辑的。如果只背出“可见性、有序性、不保证原子性”九个字一问底层就含糊基本可以判断是背面试题背出来的项目里大概率没踩过并发坑。所以这个高频问题的背后是面试官在快速筛选候选人的技术水位。1.2 从高频问题看面试官的考察层次我复盘过不少面试记录发现面试官围绕 volatile 的提问是分层的大概可以分成三层。第一层是概念层volatile 是什么有什么作用和 synchronized 什么区别这层是热身考察你有没有基本概念八股文背熟的人都能答上来。第二层是原理层volatile 为什么能保证可见性为什么不能保证原子性底层是怎么实现的什么是内存屏障这层开始筛人考察的是你是否真理解 JMM 和 CPU 硬件的工作机制。第三层是应用层什么场景适合用 volatile什么场景用了会出问题DCL 单例里为什么要加 volatile给你一个并发场景你会怎么设计这层直接区分经验和纸上谈兵没有真正写过并发代码的人到这层基本就露馅了。所以面试官层层递进地去问本质上是在验证你的知识是死的还是活的。这篇文章后续的内容其实就是按这三个层次来组织的。1.3 面试官心里那张隐形的评分表说句实在话面试官问 volatile 不是真想听你背课文而是想通过这个问题快速判断几件事。第一你的并发编程是系统学过的还是碎片化背的。系统学过的人会从 CPU 缓存模型讲到 JMM再从 JMM 讲到 volatile 的实现逻辑是通的。碎片化背的人知识点是散的说着说着就跳到 synchronized 的内存锁语义上去了。第二你有没有线上排查并发问题的经验。有经验的人会主动提到 volatile 的局限性比如 i 场景下会丢失更新比如在 JDK 8 之后 LongAdder 更适合做统计计数器比如 volatile 和原子类配合使用能写出无锁的高性能代码。这些细节不是背出来的是写出来的。第三你的表达能力。能不能把一个复杂的内存屏障机制用通俗的话讲清楚让面试官听明白本身也是一种能力的体现。搞清楚了面试官为什么问接下来进入正题把 volatile 的三板斧逐个拆开。2. volatile 核心作用拆解可见性、有序性与原子性缺角2.1 可见性解决的是“改了看不见”的问题先讲一个实际场景。假设一个开关标志控制线程是否继续运行代码大概长这样public class FlagDemo { private boolean running true; public void stop() { running false; } public void work() { while (running) { // 执行任务 } } }主线程调用 stop() 修改 running工作线程在 while 循环里读 running直觉上工作线程应该马上停下来。但真实运行起来工作线程可能永远停不下来。为什么因为 running 这个变量可能被工作线程读到了自己 CPU 缓存里的副本主线程的修改还在主内存里没有同步过来。每个 CPU 核心都有自己的高速缓存变量副本散落在各个核心的缓存中一个核心改了值另一个核心如果不通过某种机制去主内存刷新就感知不到。这就是可见性问题。volatile 就是来解决这个的。把 running 声明成 volatile每次读都强制从主内存读每次写都强制刷回主内存相当于在所有线程之间建立起一条数据同步的通道。网上很多文章喜欢用“主内存”和“工作内存”来解释 JMM虽然这个模型和真实硬件不完全一致但对理解可见性是够用的。从硬件层面看volatile 的可见性依赖的是缓存一致性协议比如 Intel 的 MESI 协议。具体机制放到后面底层原理部分再展开这里先记住结论volatile 保证了跨线程的可见性。2.2 有序性防止编译器给代码“乱序执行”有序性这个问题接触过 Java 的人基本都知道编译器为了提高性能可能会对指令进行重排。单线程下没问题因为重排保证最终结果一致但多线程下就可能出问题。经典的例子是双重检查锁DCL单例。代码是这样写的public class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }这段代码有问题。instance new Singleton()不是一个原子操作它背后分三步分配内存、调用构造器初始化、把引用赋值给 instance。在高并发下编译器和 CPU 可能把后两步重排导致某个线程拿到了一个尚未初始化完全的对象直接 NPE。解决方案就是在 instance 前面加 volatilepublic class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }加了 volatile 之后禁止指令重排保证了对象的初始化步骤按顺序完成其他线程不会拿到半初始化状态的对象。这里要补充一句volatile 的有序性指的是禁止 volatile 变量相关的读写操作被重排不是把所有代码的有序性都锁死。编译器仍然可以对不相关的代码做优化性能并不会因此全盘崩溃。2.3 原子性volatile 管不了的硬伤volatile 保证可见性和有序性但它不保证原子性。这三个特性里有一个是短板这是面试必挖的坑。原子性指的是一个操作不可分割要么全部执行要么全部不执行。典型的反例是 i。i在字节码层面是四步操作读取 i、把 i 加 1、把结果写回、返回值。即使 i 是 volatile 的这四步之间也可能被其他线程的操作插入导致两个线程同时读到相同的旧值各自加完后写回最终 i 只加了 1 而不是 2。看这段代码public class Counter { private volatile int count 0; public void increment() { count; } }开十个线程每个线程调用一万次 increment()最终 count 大概率不是十万。这就是 volatile 的原子性短板。我自己跑过这个测试用 volatile 修饰的 int 计数器十万次累加之后结果往往只有六七万数据丢失得非常稳定。要解决原子性问题需要用到 synchronized 或 JUC 包里的原子类比如 AtomicInteger它基于 CAS比较并交换实现能在无锁的情况下保证单个操作的原子性。记住一句话volatile 管的是“别人改了你能看到”原子类管的是“你改的时候别人不能插队”。为了让你更直观地对比 volatile 三个作用的边界我整理了一张表特性volatile 是否保证说明可见性保证写操作立即同步到主内存读操作从主内存读取有序性保证禁止 volatile 变量读写相关的指令重排原子性不保证复合操作如 i 仍可能被线程交替执行导致数据丢失3. volatile 底层原理从 JMM 到 CPU 缓存一致性3.1 Java 内存模型到底在说什么面试问到 volatile 原理绕不开 JMM。JMM 是 Java 虚拟机规范中定义的内存模型它描述了一组规则规定了变量在什么时候对哪些线程可见、什么时候不可见本质是解决多线程读写共享变量的内存一致性问题。JMM 的抽象结构里有两个概念主内存和工作内存。主内存是所有线程共享的存放所有变量的“权威版本”工作内存是每个线程私有的存放线程用到的变量的副本。线程不能直接操作主内存只能先把自己的工作内存副本读出来修改后再写回主内存。这个模型对应到真实硬件上主内存约等于物理内存工作内存约等于 CPU 缓存和寄存器。在这个模型下两个线程同时修改一个共享变量如果没有额外机制各自的修改都停留在自己的工作内存中互相不可见。volatile 的底层实现就是给这个模型加了两条强制规则线程对 volatile 变量的写必须立即刷新到主内存线程对 volatile 变量的读必须从主内存重新读取。这两条规则的落地靠的是一套硬件级的协议缓存一致性协议。3.2 缓存一致性协议如何保证多个 CPU 核看到同一个值现代 CPU 是多核的每个核心有自己的 L1、L2 缓存而 L3 缓存和主内存是多核共享的。当一个核心修改了某个变量其他核心的缓存里还是旧值这就需要一套机制让所有核心的缓存保持同步。这套机制就是缓存一致性协议。Intel 和 AMD 的 x86 处理器用的是 MESI 协议MESI 是四个缓存行状态的缩写Modified已修改、Exclusive独占、Shared共享、Invalid失效。当一个 CPU 核心写入 volatile 变量时处理器会发送一条信号给其他核心通知它们这个缓存行我要修改了你们手里的副本全部作废。其他核心收到通知后把对应的缓存行标记为 Invalid 状态。当它们再读这个变量时发现缓存行失效就会强制从主内存拉取最新值。这样一来所有核心对同一个变量的读写就保持一致了。有个细节值得注意volatile 变量的写操作会触发缓存行的状态变更但不会自动把数据推送到所有核心的缓存里而是要等各核心读取时主动发现缓存失效再去主内存加载。不过从 Java 规范层面看我们可以简化理解为“写后立即对其它线程可见”因为失效检测和重新加载的时延极短实际表现上是同步的。我用 MESI 状态转换图来解释一下这个过程初始状态变量被多个核心读取缓存行状态为 Shared。某个核心写入变量先将状态从 Shared 改为 Modified并广播“该缓存行失效”消息。其他核心收到消息将自己的缓存行状态从 Shared 改为 Invalid。其他核心再次读变量时发现缓存行 Invalid触发缓存缺失从主内存加载最新数据状态重新变为 Shared。这套机制保证了 volatile 的可见性不是“约等于”而是硬件层面强制的一致行为。需要说明的是MESI 协议是整个缓存一致性体系里最经典也最基础的一种实际硬件还有很多优化变体比如 MOESI、MESIF 等但面试阶段把 MESI 讲清楚已经完全足够。如果再被追问可以补充一句volatile 依赖的是底层硬件和 JVM 的结合硬件保证缓存一致性JVM 保证内存屏障的插入。3.3 内存屏障JVM 侧的关键动作缓存一致性协议解决了硬件的可见性问题但 JVM 还需要解决一个软件层面的问题指令重排。编译器和 CPU 为了优化执行效率可能会调整指令的执行顺序这在单线程下问题不大多线程下就可能破坏程序的语义。volatile 的解决方式是插入内存屏障指令。内存屏障是一种 CPU 指令它告诉处理器和编译器屏障两边的指令不能越过屏障进行重排。JVM 在 volatile 变量的读写前后会插入不同类型的内存屏障具体分四种屏障类型指令示例作用LoadLoad 屏障Load1; LoadLoad; Load2确保 Load1 先于 Load2 及后续 Load 执行LoadStore 屏障Load1; LoadStore; Store2确保 Load1 先于 Store2 及后续 Store 执行StoreStore 屏障Store1; StoreStore; Store2确保 Store1 的数据对其他处理器可见先于 Store2StoreLoad 屏障Store1; StoreLoad; Load2确保 Store1 的数据对其他处理器可见先于 Load2且开销最大volatile 的写操作会在前面插入 StoreStore 屏障在后面插入 StoreLoad 屏障。前者保证写操作之前的普通写操作不会重排到 volatile 写之后后者保证 volatile 写操作之后不能提前到写之前执行。volatile 的读操作则会在后面插入 LoadLoad 和 LoadStore 屏障保证读操作之后的普通读和普通写不会重排到 volatile 读之前。这些屏障加在一起就形成了 volatile 的有序性保证。关于内存屏障还有一点可以多说一句在 HotSpot 虚拟机里volatile 实现依赖底层平台的内存屏障指令。以 x86 平台为例由于 x86 处理器本身有较强的存储模型读读、读写屏障通常不需要显式插入最重的 StoreLoad 在写后会以 lock 前缀指令或者 mfence 指令的形式出现。所以不是所有平台都会全量插入屏障JVM 会根据平台能力做优化。3.4 通过反汇编看 volatile 的真面目前面的内容偏理论如果你感到抽象我们直接上实证。用-XX:UnlockDiagnosticVMOptions -XX:PrintAssembly参数配合 hsdis 插件可以看 volatile 变量读写编译后的汇编指令。一个加了 volatile 的 int 写操作在 x86 平台下编译后会出现一个lock前缀的指令或者mfence指令。lock 前缀指令的作用是锁住总线或者锁住缓存强制执行缓存一致性操作。这一步相当于把前面讲的 MESI 协议和内存屏障从理论拉到了实际指令层面。实操的时候环境配置有点麻烦需要下载对应系统的 hsdis-amd64.so 文件放到 JDK 的 lib 目录下再配合 JIT Watch 之类的工具查看。如果你没时间折腾可以记住结论JVM 通过 lock 前缀指令或 mfence 指令来实现 volatile 的语义。这里我推荐一个更简单的验证方式用 JMHJava Microbenchmark Harness写个基准测试对比加上 volatile 和不加 volatile 的读写性能差异。加了 volatile 的写操作因为要触发缓存同步和内存屏障性能会明显下降读操作影响相对较小。这个实验能让你直观感受到内存屏障不是免费的。4. volatile 使用场景与反模式哪些地方能用哪些地方千万别用4.1 用 volatile 做的状态标志位和开关volatile 最常见的正确用法是作为状态标志位也就是一个线程写、多个线程读的场景。典型例子是线程的取消标志public class TaskRunner { private volatile boolean cancelled false; public void cancel() { cancelled true; } public void run() { while (!cancelled) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 正常工作逻辑 } } }主线程调用 cancel()工作线程在 while 里读 cancelled由于 volatile 的可见性工作线程很快就能感知到状态变化并退出。如果把 cancelled 改成普通变量工作线程可能一直循环甚至因为 JIT 把 while 循环优化成死循环而永远停止不了。这个场景的关键在于cancelled 这个变量只有一个线程写主线程多个线程读工作线程不涉及复合操作volatile 天然适合。还有一种是“发布不可变对象”的用法。一个对象的所有字段都是 final 的构造完成后通过 volatile 引用发布给其他线程读。这种情况下volatile 不仅保证了引用可见性还配合 final 字段的初始化安全语义其他线程拿到的必然是完全构建好的对象。这种用法在实际项目里比状态标志位更高级一点但底层逻辑是一样的单写多读发布不可变快照。4.2 DCL 单例里 volatile 的必要性再强调一遍单例模式是面试必问的双重检查锁又是考点中的考点。前面已经提到过instance new Singleton()在并发下可能因为指令重排导致拿到半初始化对象加 volatile 就是根治这个问题。但这里我要多说一句很多人在 DCL 里写 volatile 是因为背了答案不理解为什么。如果面试官问“这里不加 volatile 会怎样”答案有两个层次。第一层对象初始化三步可能被重排另一个线程看到 instance 不为 null直接返回了一个构造还没完成的对象调用它的方法会抛异常或者拿到脏数据。第二层其实在 JDK 5 之前即使加了 volatileDCL 仍然有问题因为当时的 JMM 对 volatile 的语义定义不完善。JDK 5 之后JSR-133 规范重新定义了 volatile 的内存语义DCL 加 volatile 才成为标准写法。这个历史细节知道的人不多讲出来反而能加分。需要区分的是DCL 用的是“单写多读”volatile 保证引用可见性和初始化顺序如果你用静态内部类实现单例或者用枚举实现单例就不需要 volatile 了因为 JVM 的类加载机制本身就保证了线程安全。如果面试官顺着问“有哪些线程安全的单例写法”可以从饿汉式、静态内部类、枚举三条线展开。4.3 volatile 的典型反模式计数器、累积器volatile 最典型的错误用法是当计数器用。public class Counter { private volatile int count 0; public void increment() { count; } }这段代码我在前面已经演示过结果了。count 不是一个原子操作volatile 不能保证它正确。很多初学者想用 volatile 避免加锁结果数据一跑就乱。解决方式是换 AtomicIntegerpublic class Counter { private AtomicInteger count new AtomicInteger(0); public void increment() { count.incrementAndGet(); } }AtomicInteger 内部用 CAS 循环保证原子性性能比 synchronized 高不少。在大量并发累加的场景下JDK 8 还提供了 LongAdder它把竞争分散到多个 Cell 上进一步降低了 CAS 的失败重试次数吞吐量在超高并发下比 AtomicLong 更好。我踩过的一次坑是在一个消息队列消费者组里多个消费线程更新同一个 volatile 的进度字段看起来只是“读写一个 long”应该没事但读取-计算-写回这个序列就引入了竞态。最后改成 LongAdder 加定时取快照的方式才解决。所以判断一个场景能不能用 volatile就问三个问题这个变量是不是只有一个线程写写操作是否独立于读操作不依赖读到的旧值读操作是否不需要做复合判断三个问题都满足再用 volatile否则考虑锁或原子类。4.4 volatile 与 synchronized、原子类怎么选用一张表来对比 volatile、synchronized、AtomicInteger 的适用场景这样选型一目了然维度volatilesynchronizedAtomicInteger保证可见性是是是保证原子性否是是单操作保证有序性部分是部分线程阻塞否是否适用场景单写多读的状态标志复合操作或多行代码的临界区单变量计数、累加、更新性能开销较小较大涉及锁竞争中等CAS 失败会重试有个细节值得注意synchronized 既有互斥又有可见性加锁和释放锁天然地带有一写多读的同步语义。如果一个变量既需要可见性又需要原子性直接用 synchronized 更省事。如果一个变量只是作为标志位没有复合操作用 volatile 更轻量。原子类是介于两者之间的方案适合单变量的复合操作。从面试回答的角度能把选型边界讲清楚比背一堆原理更有说服力。因为选型本身就是工程能力的体现面试官会顺着这个思路追问你的实际项目场景。5. 面试实战volatile 的回答框架与高频追问应对5.1 教科书级的三分钟回答模板面试官问“讲讲 volatile”不要上来就背三个特性那样太单薄。推荐按下面的顺序组织回答既完整又有加分项。第一步先定义。volatile 是 Java 提供的轻量级同步机制用于修饰共享变量核心语义是可见性和有序性不保证原子性。第二步展开可见性。结合 JMM 说明volatile 的写会立即刷新到主内存读会从主内存重新加载。这里可以举一个标志位控制线程退出的例子一句话就能说清。第三步展开有序性。说明 volatile 通过内存屏障禁止指令重排。提 DCL 单例说明如果不加 volatile可能拿到半初始化对象。第四步承认原子性短板。主动说明 volatile 不保证原子性i 场景会丢失更新解决方式是用 AtomicInteger 或 synchronized。主动暴露短板反而显得你思考全面。如果面试官没有打断你还可以加一句“在 x86 平台上volatile 的写操作在汇编层面会给 lock 前缀或 mfence 指令这是它在硬件层的落地。”这句话一出来基本就能和背八股文的候选人拉开差距。5.2 高频追问volatile 和 synchronized 到底什么区别这个问题几乎必然会被追问回答的时候抓住三个维度区别开。第一语义不同。volatile 解决可见性和有序性synchronized 解决原子性和互斥。可以这样类比volatile 像公告栏改了就贴出来让大家看synchronized 像厕所门锁一个人进去操作的时候别人不能进操作完才开门。第二开销不同。volatile 是无锁的没有线程阻塞和上下文切换开销远小于 synchronized。synchronized 涉及锁的获取和释放存在竞争时开销明显。第三使用范围不同。volatile 只能修饰变量不能修饰方法synchronized 可以修饰方法和代码块。volatile 只能保证单个变量读写的线程安全synchronized 可以保证多行代码组成的临界区的线程安全。这里再补充一个容易被追问的点synchronized 也保证可见性。因为锁释放的时候会把线程工作内存中的变量刷新到主内存锁获取时会清空工作内存重新加载。所以 synchronized 是“顺手”保证了可见性而不像 volatile 是专门针对可见性设计的。5.3 进阶追问volatile 能替代锁吗面试官这么问的时候其实是在看你有没有能力判断工具的适用边界。回答的思路是分场景对于单一变量的状态发布场景volatile 可以替代锁并且性能更好。比如标志位控制线程启停用 synchronized 反而显得笨重。但对于复合操作比如先读旧值基于旧值计算再写回新值volatile 替代不了锁因为这不是可见性问题是原子性问题。此时要么用原子类要么用锁。可以再补充一句在高并发统计场景下LongAdder 比 AtomicLong 更快因为它把竞争分散到了多个分段上。这样的回答会让面试官觉得你不仅知道 volatile 不能替代锁还清楚替代方案在极端条件下的表现。5.4 容易被忽略的加分细节有几个冷门但实用的细节值得在面试中主动说出来。第一个是 volatile 不适用于数组和集合的复杂操作。比如volatile int[] arrvolatile 保证的是数组引用 arr 的可见性不是数组元素的可见性。元素的修改依然可能被其他线程读到旧值除非每个元素本身也是 volatile 修饰的数组做不到或使用原子数组类。第二个是 volatile 的写性能影响。每次 volatile 写都可能触发缓存行失效的广播在多核多 socket 机器上开销更大。如果你的代码对性能极其敏感尽量避免在热循环里高频写 volatile 变量可以缓冲批量写。第三个是 volatile 与 final 的配合。final 变量可以安全地发布给其他线程前提是对象在构造过程中没有逸出。如果对象引用本身通过 volatile 发布final 字段和 volatile 引用就能共同构成完美的不可变对象发布机制。这些细节本身不是 volatile 的核心知识点但组合起来看面试官会认为你有真实并发编程的沉淀而不只是会背书。6. 常见问题与并发踩坑速查表6.1 高频报错与疑问汇总把平时交流群里大家问得最多的几个问题整理成了一张速查表方便你快速定位问题现象可能原因解决方案volatile 变量在多线程累加后结果偏小volatile 不保证复合操作原子性使用 AtomicInteger 或 synchronizedDCL 单例偶尔抛 NullPointerExceptioninstance 未加 volatile初始化指令重排给 instance 加 volatile加了 volatile 的布尔标志线线程不退出while 循环被 JIT 优化成死循环确认变量是 volatile 修饰volatile 数组元素修改不可见volatile 只保证引用可见性使用 AtomicIntegerArray 或其他同步方案volatile 写导致系统性能骤降高频写触发缓存行失效广播降低写频率或采用其他同步策略这张表覆盖了我在真实项目里遇到过的大部分问题直接对照排查即可。6.2 一个完整的性能对比实验如果你对 volatile 的性能开销没有概念我建议做一个简单的 JMH 基准测试。代码大致是这样的BenchmarkMode(Mode.AverageTime) OutputTimeUnit(TimeUnit.NANOSECONDS) Warmup(iterations 5, time 1) Measurement(iterations 10, time 1) Fork(1) public class VolatileBenchmark { private int plainInt; private volatile int volatileInt; Benchmark public void plainGet() { int x plainInt; } Benchmark public void volatileGet() { int x volatileInt; } Benchmark public void plainSet() { plainInt 42; } Benchmark public void volatileSet() { volatileInt 42; } }跑出来的结果在不同机器上会有差异但趋势一致volatile 读比普通读慢几个纳秒volatile 写比普通写慢得更多在多路 CPU 环境下差距会更明显。这个实验的意义在于它提醒你 volatile 不是免费的。虽然它比锁轻量得多但在高频场景下依然有成本。设计并发组件时能不用 volatile 就不滥用能用局部变量就不用共享变量这才是并发编程的更高境界。6.3 推荐阅读路径如果这篇文章读完还想继续深入我建议按这个顺序走先看《Java 并发编程实战》的第 3 章关于可见性的部分再翻《深入理解 Java 虚拟机》中的 JMM 章节最后读 JSR-133 规范原文。这三个资料互补性很强看完之后你对 volatile 的理解会从“会背”变成“会讲”。面试前再自己动手写一遍 DCL 单例、写一遍 volatile 标志位、写一遍 AtomicInteger 计数器把这几段代码亲自跑起来比看任何笔记都管用。我面试别人时见过太多人把原理背得滚瓜烂熟但真问到他项目里怎么用的、踩过什么坑就沉默了。纸上得来终觉浅并发这块尤其如此。我自己最初对 volatile 的理解也停留在“背三句话”的层面直到线上一次偶发性的数据不一致问题把我虐了一整天才真正去翻 JSR-133、看汇编、跑压测。现在回头看那些折腾都是值得的。理解 volatile其实是理解整个 Java 并发体系的一把钥匙。这把钥匙握住了后面学 synchronized、Lock、CAS、AQS 都会顺很多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →