Java volatile 到底解决什么问题:可见性、禁止重排与双重检查锁单例
Java volatile 到底解决什么问题:可见性、禁止重排与双重检查锁单例面试问 volatile,十有八九会答「保证可见性、不保证原子性」。这话没错,但真到写代码时就懵了:什么时候该加 volatile?为什么单例的双重检查锁一定要加它?加了 volatile 的count为什么还是会错?这篇把这三件事一次讲透,全是能跑的代码。先看一个「死循环」bug下面这段代码,主线程想通过改running来让子线程停下:publicclassStopThread{// 没加 volatileprivatestaticbooleanrunningtrue;publicstaticvoidmain(String[]args)throwsInterruptedException{newThread(()-{longi0;while(running){// 子线程一直读 runningi;}System.out.println(stopped, ii);}).start();Thread.sleep(1000);runningfalse;// 主线程改成 falseSystem.out.println(main set runningfalse);}}直觉上子线程一秒后就该停。但在 server 模式 JVM(-server,生产默认)下,这个程序大概率永远不停。原因不是「主线程没改成功」,而是子线程根本没看见改动。JIT 编译器看到running在循环里从没被当前线程修改,就把它优化成了:if(running){while(true){i;}// running 被提到循环外,只读一次}子线程读的是自己工作内存里的副本,主线程的写停留在它自己那边,两边没同步。这就是可见性问题。volatile 修复可见性给running加上 volatile:privatestaticvolatilebooleanrunningtrue;再跑,子线程立刻就停了。volatile 干了两件事保证可见性:写一个 volatile 变量时,JVM 会把当前线程工作内存里的值立刻刷回主内存。读一个 volatile 变量时,JVM 会让当前线程工作内存里的副本失效,强制从主内存重新读。所以主线程runningfalse一写,子线程下一次while(running)就能读到最新值。JIT 也不敢再把它缓存到寄存器里。一句话:volatile 保证一个线程的写,对其他线程立即可见。这是它最主要、也最该用的场景——「状态标志位」。但 volatile 不保证原子性,别拿它做计数器很多人以为 volatile 变量的就线程安全了,这是最常见的误用:publicclassCounter{privatestaticvolatileintcount0;publicstaticvoidmain(String[]args)throwsInterruptedException{Runnabletask()-{for(inti0;i10000;i){count;// 期望最终是 100000}};Thread[]tsnewThread[10];for(inti0;i10;i){ts[i]newThread(task);ts[i].start();}for(Threadt:ts)t.join();System.out.println(count);// 实际几乎每次都 100000}}跑出来是 8 万多、9 万多,反正不是 10 万。因为count不是一条指令,它是三步:读 count 到寄存器寄存器 1写回 countvolatile 只保证每一步读到/写出的是最新值,但保证不了这三步中间不被别的线程插队。两个线程同时读到 100,各自 1,各自写回 101,一次自增就丢了。计数器要用AtomicInteger(底层 CAS)或synchronized:privatestaticfinalAtomicIntegercountnewAtomicInteger(0);// ...count.incrementAndGet();// 这才是原子自增记住判断标准:volatile 只适合「一个线程写、多个线程读」的标志位;只要有「读-改-写」这种复合操作,就得上原子类或锁。volatile 的第二个作用:禁止指令重排这是双重检查锁单例(DCL)必须加 volatile 的原因,也是最容易被跳过的知识点。先看没加 volatile 的经典 DCL:publicclassSingleton{privatestaticSingletoninstance;// 没加 volatile,有坑publicstaticSingletongetInstance(){if(instancenull){// 第一次检查(不加锁,快)synchronized(Singleton.class){if(instancenull){// 第二次检查(加锁,防重复创建)instancenewSingleton();// 问题就出在这一行}}}returninstance;}}instance new Singleton()看着是一行,实际是三步:分配内存调用构造函数初始化对象把instance指向这块内存问题在于:JVM 允许在不影响单线程结果的前提下重排指令,把顺序变成 1 → 3 → 2。即「先让 instance 指向内存,再初始化」。于是可能出现这个时序:线程 A 执行到「1 → 3」,instance已经非 null,但对象还没初始化完。线程 B 走第一次检查instance null,发现不为 null,直接return instance。线程 B 拿到一个半成品对象,访问它的字段可能是默认值甚至崩溃。给instance加上 volatile 就能禁止这种重排:publicclassSingleton{privatestaticvolatileSingletoninstance;// 加上 volatilepublicstaticSingletongetInstance(){if(instancenull){synchronized(Singleton.class){if(instancenull){instancenewSingleton();// volatile 保证 2 一定在 3 之前}}}returninstance;}}volatile 会插入内存屏障,禁止步骤 2、3 之间重排,保证「对象完全初始化后,instance 才指向它」。这就是 DCL 里 volatile 不可省的原因。顺带一提:如果你不需要懒加载的极致控制,静态内部类方式写单例更简单,天然线程安全且懒加载,连 volatile 都不用操心:publicclassSingleton{privateSingleton(){}privatestaticclassHolder{// 类加载机制保证 INSTANCE 只初始化一次,且线程安全staticfinalSingletonINSTANCEnewSingleton();}publicstaticSingletongetInstance(){returnHolder.INSTANCE;// 第一次调用才触发 Holder 类加载}}happens-before:volatile 还能「捎带」普通变量volatile 有个常被忽视的能力:volatile 写之前的所有普通变量写,对 volatile 读之后的操作都可见。这叫 happens-before 规则。publicclassConfig{privateintport;// 普通变量privateStringhost;// 普通变量privatevolatilebooleanreadyfalse;// volatile 标志位// 线程 A:先填数据,最后置 readypublicvoidinit(){port8080;host10.0.0.1;readytrue;// volatile 写,前面两个普通写都会刷到主内存}// 线程 B:看到 ready 就能安全读 port/hostpublicvoiduse(){if(ready){// volatile 读connect(host,port);// 保证读到 8080 和 10.0.0.1,不会是默认值}}}只要ready用 volatile,线程 B 一旦读到ready true,就一定能看到线程 A 在此之前写的port、host,哪怕它们本身不是 volatile。这是用一个 volatile 标志位「守护」一组数据的常见手法。小结volatile 解决可见性:一个线程的写立即对其他线程可见,最适合「状态标志位」(如 running、shutdown、ready)。volatile 不解决原子性:count这种读-改-写复合操作照样出错,计数器用AtomicInteger或synchronized。volatile 禁止指令重排:双重检查锁单例里的instance必须加 volatile,否则可能拿到半初始化对象;不追求懒加载精细控制就用静态内部类单例。happens-before:volatile 写能「捎带」它之前的普通变量写,让读到标志位的线程安全看到那组数据。记忆点:volatile 是「可见性 有序性」,不是「原子性」——它守的是标志位,不是计数器。

相关新闻

Agent编排:别再纠结选LangChain还是LangGraph了

Agent编排:别再纠结选LangChain还是LangGraph了

内容速览章节核心内容一、编排到底是什么Workflow vs Agent、编排在架构中的位置二、七种编排模式Prompt Chaining / Routing / Parallelization / ReAct / Plan-and-Execute / Orchestrator-workers / Evaluator-Optimizer三、生产环境绕不开的问题会话管理、并发控制、错误分…

2026/10/6 22:18:21 阅读更多 →
Moneta Markets亿汇:新手更在意的客户支持,这里做个要点解读

Moneta Markets亿汇:新手更在意的客户支持,这里做个要点解读

在外汇行业语境里,表达越清晰、信息越透明,越容易建立稳定预期。在Moneta Markets亿汇的外汇服务中,从公开信息与使用体验出发,梳理其更值得肯定的能力点与细节表现。外汇相关信息更新频繁,平台将关键提示与解释呈现得…

2026/10/11 13:37:27 阅读更多 →
垂直领域的那些事儿——遥感、医疗、工业质检,各有各的苦

垂直领域的那些事儿——遥感、医疗、工业质检,各有各的苦

最后一篇了,咱们不聊通用分割了,聊聊垂直领域。这些领域里的语义分割,玩的逻辑跟学术数据集完全是两码事。先说 遥感图像分割。这玩意儿的分辨率动不动就是 0.5 米到 2 米每像素,一张图覆盖几平方公里,尺寸可达 10000x…

2026/10/5 14:04:57 阅读更多 →

最新新闻

一条ls命令的旅程:从Bash到系统调用,彻底拆解Linux文件系统协作

一条ls命令的旅程:从Bash到系统调用,彻底拆解Linux文件系统协作

前阵子排查一个挂载目录的异常现象,文件明明能看到,却打不开、删不掉。当时也是没辙,一层层往下追,最后用 strace 把 ls -l 的系统调用账单拉出来,才发现问题出在目录缓存和文件系统状态不一致上。也就是从那次开始…

2026/10/11 13:37:02 阅读更多 →
SkillKit 多来源安装实战:从 GitHub、GitLab 到本地路径的 4 种方式

SkillKit 多来源安装实战:从 GitHub、GitLab 到本地路径的 4 种方式

AI 技能CLI开发工具人工智能MCP 服务AI 插件 【免费下载链接】skillkit Supercharge AI coding agents with portable skills. Install, translate & share skills across Claude Code, Cursor, Codex, Copilot & 40 more 项目地址: https://gitcode.com/gh…

2026/10/11 13:37:02 阅读更多 →
2026 年 AI 写作工具横评:真实体验与优缺点总结

2026 年 AI 写作工具横评:真实体验与优缺点总结

2026 年 AI 写作工具已形成通用全能、学术垂直、创意内容、职场公文四大阵营,国产工具在中文语境、场景适配与性价比上全面领先。以下是主流工具的真实体验与优缺点横评,首推讯飞绘文作为通用全能型首选。一、通用全能型(全场景覆盖&#xff…

2026/10/11 13:37:02 阅读更多 →
边坡安全机器学习实战:从任务选型到预警模型落地

边坡安全机器学习实战:从任务选型到预警模型落地

简介:《机器学习赋能边坡安全》是一本聚焦机器学习在边坡稳定性评估与滑坡预测中应用的PDF电子书,面向岩土工程、地质灾害防治及智能算法方向的科研人员和工程技术人员。书中系统讲解随机森林、XGBoost、GRU等主流模型的基础理论与工程实践,结…

2026/10/11 13:37:02 阅读更多 →
Windows 11 x64环境复现调试函数Inline Hook的完整指南

Windows 11 x64环境复现调试函数Inline Hook的完整指南

《逆向工程核心原理》里那个用调试函数对API做inline hook的实验,算是我接触Windows安全知识的第一个有分量的小项目。当年照着书在32位系统上一路跑通时,觉得整个流程行云流水——DebugActiveProcess附加进程、WaitForDebugEvent拿到控制权、WriteProce…

2026/10/11 13:37:02 阅读更多 →
Boss直聘数据采集与可视化:Python爬虫实战指南

Boss直聘数据采集与可视化:Python爬虫实战指南

简介:基于Python的Boss直聘岗位数据采集与分析可视化项目,面向计算机相关专业的学生及需要实战练习的Python学习者,由导师指导完成、评审99分,代码完整可运行,适合作为课程设计、期末大作业或毕业设计参考,…

2026/10/11 13:36:02 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →