当前位置: 首页 > news >正文

Java 内存模型(JMM)面试清单(含超通俗生活案例与深度理解)

一、说一下你对 Java 内存模型(JMM)的理解?

核心定义:Java 内存模型(JMM)是 Java 虚拟机为了消除不同硬件(如 Intel x86、ARM 架构)、不同操作系统(如 Windows、Linux)在内存访问上的差异,而制定的一套抽象规则体系。它不对应真实的物理内存结构,而是通过约定线程与主内存、线程与本地内存的交互逻辑,确保 Java 程序在任何运行环境下,都能表现出一致的并发行为。
具体来说,JMM 有两个关键约定:一是所有线程共享的变量(如类的静态变量、实例变量)必须存放在主内存(可理解为计算机的物理内存)中;二是每个线程都有专属的本地内存(抽象概念,包含 CPU 缓存、寄存器、写缓冲区等硬件组件),线程读写共享变量时,不能直接操作主内存,必须先将主内存中的变量副本加载到本地内存,修改后再将副本刷新回主内存。

原创生活例子:可以类比“社区共享储物间与住户个人储物盒”——社区的公共储物间(主内存)存放所有住户都能使用的工具(共享变量),比如梯子、扳手;每个住户(线程)家里都有一个储物盒(本地内存),需要用梯子时,不会直接把储物间的梯子搬回家长期使用,而是先把梯子搬到自己的储物盒(加载副本),用完后再放回储物间(刷新回主内存)。如果住户 A 用梯子时发现梯子坏了,修好后放回储物间,住户 B 再用梯子时,会从储物间拿修好的版本(加载最新副本),而不是用自己储物盒里之前坏的梯子(旧副本)。这里的“储物盒”就是 JMM 中的本地内存,它让住户不用关心储物间的“管理细节”(比如储物间有多少层货架),只需按“取→用→还”的规则操作,就不会出现“A 修好了梯子,B 却还用坏梯子”的问题——这正是 JMM 屏蔽平台差异、保证并发一致性的核心价值。

深入思考:如果没有 JMM,同样的 Java 代码在不同设备上可能出现截然不同的并发问题。比如在有 CPU 缓存的电脑上,线程 A 修改共享变量后,数据只存在缓存里没刷到主内存,线程 B 读的是主内存的旧值,就会出现“数据不一致”;而在无缓存的嵌入式设备上,线程 B 能直接读主内存的新值,结果就正确。JMM 从语言层面统一了这种差异,让程序员不用深入研究硬件原理,只需遵循 JMM 的规则,就能写出在任何平台都正确的并发代码。

二、说说你对原子性、可见性、有序性的理解?

JMM 的核心目标是解决并发编程中的三大核心问题——原子性、可见性、有序性,这三者是保证并发程序正确运行的基石,缺少任何一个都可能导致程序出现逻辑错误。

1. 原子性:“要么全成,要么全不成”

核心定义:原子性指一个操作是“不可分割、不可中断”的整体,整个操作要么完整执行(中间不会被其他线程打断),要么完全不执行,绝不存在“执行了一半就暂停”的中间状态。
原创生活例子:可以类比“外卖点单支付”——你在手机上点一份奶茶,点击“支付”后,整个操作要么成功(账户扣款、商家收到订单),要么失败(账户不扣款、商家收不到订单),不会出现“扣了钱但商家没订单,或者商家有订单但没扣钱”的情况。但如果是“先下单、后线下付款”,就不是原子操作——你下了单但没付款,商家可能以为你会付款而预留奶茶,结果你没付,导致商家损失,这就是非原子操作的风险。
Java 中的原子性场景:

• 基本类型的赋值操作(如 int score = 90、boolean isDone = false)是原子的,因为这些操作在 JVM 中只对应一条指令,无法拆分成更小的步骤;

• 复合操作(如 score++、total = a + b、user.setAge(20))是非原子的——以 score++ 为例,它实际包含三步:读取 score 的当前值(90)→计算 score + 1(91)→将 91 写回 score,中间如果被其他线程打断,可能出现“两个线程同时执行 score++,最终 score 只加了 1 而不是 2”的错误。

2. 可见性:“我改了,你得立刻看见”

核心定义:可见性指当一个线程修改了共享变量的值后,其他线程能“立即感知到这个修改”,不会继续使用自己本地内存中缓存的旧值。简单说,就是一个线程对共享变量的修改,对所有其他线程都是“透明且实时的”。
原创生活例子:可以类比“班级值日表”——班级的值日表贴在教室黑板(主内存)上,班长(线程 A)负责更新值日表,某天发现小明请假,把“小明值日”改成“小红值日”(修改共享变量),改完后把值日表重新贴回黑板(刷新到主内存)。小红(线程 B)来教室后,不会凭昨天的记忆认为是小明值日,而是看黑板上的最新值日表(从主内存加载),直接知道自己要值日。如果班长改完值日表后,没贴回黑板(没刷新到主内存),而是放在自己抽屉里,小红看的还是旧值日表,就会错过值日——这就是可见性问题导致的错误。
Java 中的可见性风险:如果没有可见性保证,线程 A 将共享变量 flag 从 false 改为 true,线程 B 可能一直读取自己 CPU 缓存中的 flag = false,导致线程 B 循环判断“flag 为 true 就退出”时,永远无法退出,程序陷入死循环。

3. 有序性:“该先的不后,该后的不先”

核心定义:有序性指程序执行的顺序——单线程下,程序看起来是“按代码书写的顺序执行”;但多线程下,编译器或 CPU 为了提高执行效率,会对“没有数据依赖的指令”做重排序,导致实际执行顺序与代码顺序不一致,这就是有序性问题。
原创生活例子:可以类比“周末大扫除”——你的计划(代码顺序)是:1. 擦桌子(A)→2. 扫地(B)→3. 拖地(C)。其中 A 和 B 没有数据依赖(擦桌子和扫地可以同时做,不用等擦完桌子再扫地),CPU 可能会重排为“先扫地(B)→再擦桌子(A)”;而 C 依赖 B(必须等扫完地,地面没有垃圾,才能拖地),所以 C 不能排在 B 前面。重排后顺序可能是“B→A→C”,但最终结果还是“桌子干净、地面整洁”,单线程下没问题。但如果是多线程:你负责扫地(线程 A),妈妈负责拖地(线程 B),如果你的“扫地”被重排到妈妈“拖地”之后,妈妈就会拖还没扫的地,地面依然有垃圾——这就是多线程下有序性问题导致的错误。

三大特性的保证方式:

• 原子性:JMM 仅保证基本赋值操作的原子性,复杂操作需借助 synchronized(加锁让多线程串行执行,避免打断)或 java.util.concurrent.atomic 包(如 AtomicInteger,底层用 CAS 操作将多步操作变成原子操作);

• 可见性:可通过 volatile(写操作后强制刷新主内存,读操作前强制清空本地缓存)、synchronized(解锁前刷新主内存,加锁前加载主内存最新值)、final(final 变量初始化后不可修改,其他线程看到的一定是初始化后的正确值)保证;

• 有序性:可通过 volatile(禁止特定指令重排)、synchronized(同一时间只有一个线程执行,避免重排干扰)保证,或依赖 JMM 的 happens-before 规则(通过逻辑顺序约束执行顺序)。

三、那说说什么是指令重排?

核心定义:指令重排是编译器、CPU 为了提升程序执行效率,在“不改变单线程执行结果”的前提下,对“没有数据依赖的指令”调整执行顺序的优化行为。它主要分为三类:

1. 编译器优化重排:编译器在编译代码时,根据代码逻辑调整语句的执行顺序(比如把无依赖的变量赋值提前);

2. 指令级并行重排:现代 CPU 支持“多条指令重叠执行”,如果指令间没有数据依赖,CPU 会打乱指令的执行顺序,让硬件资源更高效;

3. 内存系统重排:CPU 缓存、写缓冲区会导致读写操作的实际执行时间与代码顺序偏差(比如写操作先存在缓冲区,没立即刷到主内存,看起来像“晚执行”)。

原创生活例子:可以类比“快递驿站打包流程”——驿站打包一份快递(代码)需要三个步骤:1. 贴快递单(A)→2. 把商品装袋(B)→3. 称重记录(C)。其中 A 和 B 没有数据依赖(贴单和装袋可以同时做,不用等贴完单再装袋),打包员(编译器/CPU)可能会先装袋(B)再贴单(A);而 C 依赖 B(必须等商品装袋后,才能称重),所以 C 不能排在 B 前面。重排后顺序可能是“B→A→C”,但最终还是打包出一份完整的快递,效率比按原顺序高。但如果是多线程打包:打包员 A 负责装袋(B),打包员 B 负责称重(C),如果 A 的“装袋”被重排到 B 的“称重”之后,B 就会称空袋子,导致重量记录错误——这就是多线程下指令重排的风险。

指令重排的典型风险(多线程场景):
最常见的是“对象创建”操作,代码层面写的是 Order order = new Order(),但 JVM 会把这个操作拆成三步:

1. 给 Order 对象分配内存空间(A);

2. 初始化 Order 对象的成员变量(比如给 orderId 赋值“123”,B);

3. 把 order 变量指向分配好的内存空间(让 order 从 null 变成非 null,C)。
由于 B 和 C 没有数据依赖(初始化对象和指向内存是两个独立步骤),编译器可能把顺序重排为“A→C→B”。这时如果线程 A 执行到“C”(order 非 null,但还没初始化 orderId),线程 B 来判断“如果 order != null 就获取 orderId”,会误以为对象已创建完成,直接调用 order.getOrderId(),结果拿到 orderId 的默认值(null),导致空指针异常——这也是为什么双重校验单例模式中,instance 必须用 volatile 修饰,目的就是禁止这三步的重排。

深入思考:指令重排的本质是“以结果不变为前提的效率优化”,单线程下不会有问题(重排不影响最终结果),但多线程下会打破“执行顺序”,让其他线程看到错误的“中间状态”(比如 order 非 null 但未初始化)。JMM 解决这个问题的思路不是“禁止所有重排”(那样会大幅降低效率),而是“禁止会导致多线程错误的重排”(比如用 volatile 禁止对象创建的重排),在效率和正确性之间找到平衡。

四、指令重排有限制吗?happens-before 了解吗?

指令重排不是“无拘无束”的,JMM 有两大核心规则约束它:as-if-serial 语义(约束单线程下的重排)和 happens-before 规则(约束多线程下的重排),确保无论怎么重排,程序的正确性都不会被破坏。

1. 指令重排的第一重限制:as-if-serial 语义

核心定义:as-if-serial 语义的核心是“不管编译器、CPU 怎么重排,单线程程序的最终执行结果,必须和‘严格按代码顺序执行’的结果完全一致”。编译器、JVM 运行时、CPU 都必须遵守这个规则,因此有数据依赖的指令绝对不能重排,只有无数据依赖的指令才允许重排。

原创生活例子:可以类比“冲速溶咖啡”——你的操作步骤(代码顺序)是:1. 拿一个杯子(A)→2. 倒速溶咖啡粉(B)→3. 冲热水(C)→4. 搅拌均匀(D)。其中 A 和 B 没有数据依赖(可以先倒咖啡粉再拿杯子,只要冲热水前准备好杯子和粉就行),所以可以重排为“B→A→C→D”;但 C 依赖 A 和 B(必须有杯子、有咖啡粉,才能冲热水),D 依赖 C(必须冲了热水,才能搅拌),所以 C 不能排在 A、B 前面,D 不能排在 C 前面。重排后,你依然能冲好一杯能喝的咖啡,和按原顺序执行的结果一致——这就是 as-if-serial 语义允许的重排。但如果重排为“C→A→B→D”(没杯子就冲热水,没咖啡粉就搅拌),结果会是“热水洒了、没咖啡味”,违反 as-if-serial 语义,这种重排是绝对禁止的。

as-if-serial 语义给单线程程序员吃了“定心丸”:不用关心底层有没有重排,只要代码逻辑没问题,单线程程序的结果就一定正确。

2. 指令重排的第二重限制:happens-before 规则

核心定义:happens-before 是 JMM 定义的一套“操作间可见性和有序性的约束关系”,它的核心作用是:如果操作 A happens-before 操作 B,那么 A 的执行结果一定对 B 可见,且 A 的逻辑执行顺序一定在 B 之前。注意:这里的“顺序”是“逻辑顺序”,不是“物理顺序”——即使 A 物理上比 B 晚执行,只要 B 能看到 A 的结果,就满足 happens-before 关系;如果重排后 B 依然能看到 A 的结果,这种重排就是允许的。

JMM 中有六大与日常开发密切相关的 happens-before 规则,每个规则都能用生活场景直观理解:

(1)程序顺序规则:线程内,前操作 happens-before 后操作

规则解释:在同一个线程中,代码写在前面的操作,happens-before 写在后面的任意操作。
原创生活例子:学生做手工(一个线程),步骤是“1. 剪彩纸(A)→2. 粘胶水(B)→3. 穿绳子(C)”。根据规则,A happens-before B,B happens-before C——学生必须先剪好彩纸,才能粘胶水;先粘好胶水,才能穿绳子。即使学生因为“彩纸难剪,先粘胶水的其他部分,最后补剪彩纸”(物理顺序重排),但补剪后粘胶水、穿绳子的结果依然正确(逻辑上 A 还是在 B 前),这种重排是允许的。

(2)监视器锁规则:解锁操作 happens-before 后续加锁操作

规则解释:对同一个锁(比如 synchronized 修饰的对象),如果线程 A 先解锁,那么 A 的解锁操作 happens-before 线程 B 后续对这个锁的加锁操作。
原创生活例子:办公室有一台共享打印机(锁对象),只有一个打印任务队列(锁)。员工 A 先发送打印任务(加锁),打印完成后释放队列(解锁);员工 B 要打印,必须等 A 解锁后才能发送任务(加锁)。根据规则,A 的解锁 happens-before B 的加锁——B 打印时,能看到 A 打印后的队列状态(比如 A 打印完清空了队列),不会出现“A 还在打印,B 就强行插入任务”的混乱。

(3)volatile 变量规则:写操作 happens-before 后续读操作

规则解释:对同一个 volatile 变量,如果线程 A 先写,那么 A 的写操作 happens-before 线程 B 后续对这个变量的读操作。
原创生活例子:小区电梯里的通知屏(volatile 变量),物业(线程 A)更新通知为“电梯维护,1 号梯停用”(写操作),居民(线程 B)乘电梯前看通知屏(读操作)。根据规则,A 的写 happens-before B 的读——居民看到的一定是“1 号梯停用”的最新通知,不会看到昨天的“电梯正常运行”旧通知,避免走错电梯。

(4)传递性规则:A happens-before B,B happens-before C → A happens-before C

规则解释:如果 A 比 B 先发生,B 比 C 先发生,那么 A 一定比 C 先发生。
原创生活例子:妈妈(A)告诉爸爸“家里酱油没了,买一瓶”(B),爸爸告诉孩子“去超市买酱油”(C)。根据规则,A happens-before B,B happens-before C → A happens-before C——孩子最终知道“买酱油”,本质是因为妈妈的初始指令,这就是传递性的体现。即使孩子没听到爸爸的话,直接听到妈妈的话,同样能知道买酱油,结果一致。

(5)start() 规则:start() 操作 happens-before 线程内任意操作

规则解释:如果线程 A 执行 threadB.start() 启动线程 B,那么 A 的 start() 操作 happens-before 线程 B 中的所有操作。
原创生活例子:老师(线程 A)组织小组活动,对小组 B(线程 B)说“开始制作手抄报”(start()),小组 B 收到指令后,才开始“画边框、写文字”等操作。根据规则,老师的 start()  happens-before 小组的操作——小组不会在老师没说“开始”前就动手,确保操作有正确的启动顺序。

(6)join() 规则:线程内操作 happens-before join() 返回后操作

规则解释:如果线程 A 执行 threadB.join() 并等待线程 B 完成,那么线程 B 中的所有操作 happens-before 线程 A 从 join() 返回后的操作。
原创生活例子:小组做课堂报告,组员 B(线程 B)负责准备 PPT(操作),组长 A(线程 A)等 B 准备好 PPT(join()),再汇总 PPT 和演讲稿提交给老师(返回后的操作)。根据规则,B 的准备操作 happens-before A 的汇总操作——A 汇总时,能拿到 B 准备好的完整 PPT,不会出现“B 还没做完 PPT,A 就提交空文件”的情况。

深入思考:happens-before 规则是 JMM 给程序员的“并发判断工具”——不用深入分析底层重排,只需根据规则判断“两个操作之间是否有 happens-before 关系”:如果有,就不用担心可见性和有序性问题;如果没有,就需要用 volatile 或 synchronized 补充约束。比如线程 A 写普通变量 a,线程 B 读 a,两者没有 happens-before 关系,可能出现 B 看不到 A 的修改,这时候把 a 声明为 volatile,通过“volatile 变量规则”建立 happens-before 关系,就能解决问题。

五、as-if-serial 又是什么?单线程的程序一定是顺序的吗?

核心定义:as-if-serial 语义是 JMM 对单线程程序的“核心承诺”——无论编译器、CPU 怎么进行优化重排,单线程程序的最终执行结果,必须和“严格按代码书写顺序执行”的结果完全一致。简单说,单线程下“重排可以有,但结果不能变”。

原创生活例子:可以类比“煮方便面”——你的操作步骤(代码顺序)是:1. 拿锅(A)→2. 加水(B)→3. 开火(C)→4. 放面饼(D)→5. 加调料(E)。其中 A、B、C 之间,A 和 B 无依赖(可以先加水再拿锅,只要开火前锅和水准备好),B 和 C 无依赖(可以先开火再加水,只要水没烧干前放面饼),所以可以重排为“B→A→C→D→E”;但 D 依赖 C(必须等水开,才能放面饼),E 依赖 D(必须放了面饼,才能加调料),所以 D 不能排在 C 前,E 不能排在 D 前。重排后,你依然能煮出“熟面饼+有味道”的方便面,和按原顺序执行的结果一致——这就是 as-if-serial 语义允许的重排。但如果重排为“D→C→B→A→E”(没锅没水就放面饼,没开火就煮),结果会是“面饼没熟、没味道”,违反 as-if-serial 语义,这种重排是绝对禁止的。

单线程的程序一定是顺序的吗?

答案:单线程程序“物理执行顺序不一定是代码顺序,但逻辑结果一定是顺序的”。
比如煮方便面的例子,物理上你可能先加水(B)再拿锅(A)(重排),但逻辑上“拿锅”还是在“放面饼”前,“加水”还是在“开火”前——as-if-serial 语义让程序员“感知不到重排的存在”,仿佛程序就是按代码顺序执行的。

为什么会这样?因为编译器和 CPU 会在遵守 as-if-serial 语义的前提下,偷偷做优化:

• 对有数据依赖的指令(比如 D 依赖 C),绝对不重排,确保逻辑顺序正确;

• 对无数据依赖的指令(比如 A 和 B),可以重排,充分利用硬件资源(比如 CPU 空闲时提前做准备步骤),提升执行效率。

深入思考:as-if-serial 语义是单线程程序“不用关心并发问题”的根本原因。比如你写 int x = 5; int y = x + 3;,不用担心 CPU 会先算 y = x + 3 再赋值 x = 5——因为这两步有数据依赖(y 依赖 x 的值),重排会导致 y 为 3 而不是 8,违反 as-if-serial 语义,编译器和 CPU 绝对不会这么做。但多线程下,as-if-serial 语义会失效——比如线程 A 煮方便面(加水、开火),线程 B 加调料,线程 B 可能在水没开时就加调料,导致结果错误,这时候就需要额外的同步手段(如 synchronized)来保证顺序。

六、volatile 实现原理了解吗?

volatile 是 Java 并发编程中“轻量级”的关键字,它不能保证原子性,但能有效保证共享变量的可见性和有序性,底层通过“主内存读写规则”和“内存屏障”实现,没有 synchronized 那样的“加锁-解锁”上下文切换开销,执行效率更高。

1. volatile 如何保证可见性?

核心原理:当变量被声明为 volatile 时,JMM 会强制线程对该变量的“读”“写”操作绕开本地内存,直接与主内存交互,具体有两条严格规则:

• 写操作规则:线程修改 volatile 变量后,必须立即将新值“刷新到主内存”,不能存放在 CPU 缓存或写缓冲区中,确保主内存中的值是最新的;

• 读操作规则:线程读取 volatile 变量前,必须先“清空本地内存中的旧副本”,然后从主内存加载最新值,不能使用本地缓存里的旧值。

原创生活例子:可以类比“奶茶店的取餐号屏”——奶茶店的取餐号屏(volatile 变量)挂在前台(主内存),店员(线程 A)做完奶茶后,会立即更新取餐号屏(比如从“10 号”改成“11 号”,写操作),屏上的号码会实时变化(刷新到主内存);顾客(线程 B)取餐时,不会记着自己之前看到的“10 号”(本地缓存旧值),而是看当前屏上的最新号码(从主内存加载),如果显示“11 号”,就知道自己的奶茶好了。如果店员改了号但没更新屏(没刷新主内存),顾客看的还是旧号,就会错过取餐——这就是 volatile 避免的可见性问题。

对比普通变量:如果取餐号是店员记在纸上(普通变量),店员改了号但没更新纸(没刷新主内存),顾客问店员时,店员可能随口说旧号(用本地缓存),导致顾客等错号,对应程序中的“可见性缺失”。

2. volatile 如何保证有序性?

核心原理:volatile 通过“插入内存屏障”(一种特殊的 CPU 指令,作用是禁止特定类型的指令重排),阻止编译器和 CPU 对 volatile 变量相关的指令做重排。JMM 为 volatile 变量制定了三条严格的重排禁止规则:

• 禁止将 volatile 写操作之前的普通指令,重排到写操作之后;

• 禁止将 volatile 读操作之后的普通指令,重排到读操作之前;

• 禁止将 volatile 写操作,重排到另一个 volatile 读操作之前。

原创生活例子:可以类比“奶茶店的取餐叫号员”(内存屏障)——叫号员负责维护取餐顺序,确保 volatile 变量(取餐号)的读写顺序不混乱:

1. 店员做完奶茶(普通指令)后,必须先更新取餐号屏(volatile 写操作),才能叫顾客取餐(禁止写前指令重排到写后);

2. 顾客听到叫号(volatile 读操作)后,必须先取餐(普通指令),才能离开(禁止读后指令重排到读前);

3. 店员更新取餐号(volatile 写)后,顾客才能听到叫号(volatile 读),不能顾客先听号再更新(禁止写重排到读前)。
通过这些规则,取餐顺序永远是“做奶茶→更新号→叫号→取餐”,不会出现“叫了号但奶茶没做好”的混乱——这就是 volatile 保证有序性的效果。

具体内存屏障的插入规则:
JMM 规定,编译器生成字节码时,必须在 volatile 变量的读写操作前后插入以下四种内存屏障(不同屏障对应不同的重排禁止逻辑):

• StoreStore 屏障:插入在 volatile 写操作前,作用是“确保前面所有普通写操作都已刷新到主内存,再执行当前 volatile 写”。比如先写普通变量 a,再写 volatile 变量 b,StoreStore 屏障会让 a 的值先刷到主内存,再写 b,避免 b 写了但 a 没刷,导致其他线程看到 b 新值却看不到 a 新值;

• StoreLoad 屏障:插入在 volatile 写操作后,作用是“确保当前 volatile 写已刷新到主内存,再执行后续的读操作”。比如写 volatile 变量 b 后,读普通变量 c,StoreLoad 屏障会让 b 的新值刷到主内存,再读 c,避免 c 读了但 b 没刷,导致读 c 时用的还是旧逻辑;

• LoadLoad 屏障:插入在 volatile 读操作后,作用是“确保当前 volatile 读已加载到本地,再执行后续的普通读操作”。比如读 volatile 变量 b 后,读普通变量 c,LoadLoad 屏障会让 b 的最新值加载完成,再读 c,避免 c 读早了,用的还是旧值;

• LoadStore 屏障:插入在 volatile 读操作后,作用是“确保当前 volatile 读已加载到本地,再执行后续的普通写操作”。比如读 volatile 变量 b 后,写普通变量 c,LoadStore 屏障会让 b 的最新值加载完成,再写 c,避免 c 写早了,逻辑基于 b 的旧值。

3. volatile 的局限性:不能保证原子性

核心原因:volatile 只能约束“单个读写操作”的可见性和有序性,但无法覆盖“多步复合操作”。比如 volatile int count = 0,执行 count++ 时,依然包含“读 count(0)→加 1(1)→写 count(1)”三步,中间可能被其他线程打断。假设两个线程同时执行 count++,可能出现“两个线程都读 0,都加 1,都写 1”的情况,最终 count 是 1 而不是 2——这就是原子性缺失导致的错误。

解决办法:如果需要保证原子性,有两种常用方案:一是用 synchronized 加锁,让 count++ 串行执行(同一时间只有一个线程能执行);二是用 java.util.concurrent.atomic 包下的 AtomicInteger,它底层用 CAS(Compare And Swap)操作,将“读-加-写”三步变成一个原子操作,避免被打断。

深入思考:volatile 是“轻量级”的并发工具,最适合的场景是“多线程读、单线程写”,比如状态标记位(volatile boolean isRunning = true,一个线程改 isRunning = false,其他线程读 isRunning 判断是否退出循环)。在这种场景下,volatile 比 synchronized 效率高得多,因为它没有锁竞争的开销。但如果是“多线程写”场景(比如多个线程同时修改 count),volatile 无法保证原子性,必须用更重量级的同步手段,这也是并发编程中“权衡效率与正确性”的典型体现——没有“万能工具”,只有“适合场景的工具”。

 

http://www.dtcms.com/a/441775.html

相关文章:

  • 2015网站建设专业建网站设计公司
  • vue+springboot项目部署到服务器
  • QT肝8天17--优化用户管理
  • QT肝8天19--Windows程序部署
  • 【开题答辩过程】以《基于 Spring Boot 的宠物应急救援系统设计与实现》为例,不会开题答辩的可以进来看看
  • 成都seo网站建设沈阳网站建设推广服务
  • 网站栏目名短链接在线生成官网免费
  • Task Schemas: 基于前沿认知的复杂推理任务架构
  • 第三十七章 ESP32S3 SPI_SDCARD 实验
  • 企业营销型网站特点企业信息查询系统官网山东省
  • docker-compose 安装MySQL8.0.39
  • Go语言入门(18)-指针(上)
  • Django ORM - 聚合查询
  • 【STM32项目开源】基于STM32的智能老人拐杖
  • YOLO入门教程(番外):卷积神经网络—汇聚层
  • 网站改版一般需要多久智慧团建学生登录入口
  • Dotnet接入AI通过Response创建一个简单控制台案例
  • 【论文笔记】2025年图像处理顶会论文
  • 用 Maven 配置 Flink 从初始化到可部署的完整实践
  • 做职业规划的网站seo学院
  • 怎么建优惠券网站太原seo排名外包
  • jmeter中java.net.ConnectException: Connection refused: connect
  • “十四五”科技冲锋:迈向科技强国的壮阔征程
  • 使用 Python 进行自然语言处理的完整初学者指南
  • 框架系统的多维赋能——论其对自然语言处理深层语义分析的影响与启示
  • HCIP 和 HCIE到底是报班还是自学好?
  • 网站建设要多少钱国外服装设计网站
  • Spring配置文件XML验证错误全面解决指南:从cvc-elt.1.a到找不到‘beans‘元素声明
  • 做美食视频网站有哪些网架公司招聘施工队伍
  • Qwen3-Coder 实战:从 0 到 1 开发商业级 API 平台,过程开源!