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

第一次学 volatile 关键字,我看了三遍才搞懂它到底在干嘛

学 Java 并发编程的时候,第一个让我卡住的关键字就是 volatile。

网上搜了一圈,全是"可见性"“指令重排序”“内存屏障”——每个字都认识,放在一起我就不知道它们在说什么了。

说实话我看了好几遍,又去翻了点资料,总算理清楚了一点。这篇就当是我自己整理的学习笔记。可能有些地方说得不够严谨,但至少是我自己的理解。如果有哪里写错了,欢迎指出来,我也还在学。


先说 volatile 到底是干嘛的

volatile 是 Java 给变量加的一个关键字。

你不加它,变量在多线程下可能出问题。你加了它,就相当于告诉 JVM:“这个变量你给我小心点处理,别自作聪明。”

它主要做两件事。我分开来说。

第一件事:让一个线程改了,其他线程能看见

这个叫"可见性"。

我打个比方。假设你和几个同事一起做一个项目,项目文档放在共享文件夹里。但是每个人为了方便,都把文档在本地电脑打开了一份。现在你修改了文档,保存了。你的同事看自己本地那版——还是旧的内容。

这就是多线程里的问题。

Java 里每个线程都有自己的"工作内存",相当于本地缓存。线程读变量的时候,先读到自己这边来;改了之后,也不一定马上写回主内存。如果不用 volatile,别的线程可能根本看不到你改了。

加上 volatile 之后,Java 就会保证:你写的时候立即刷回主内存,别人读的时候直接从主内存读。相当于你在共享文档上点了"保存并同步",所有人都能看到最新版。

第二件事:不让编译器和处理器乱调顺序

这个叫"禁止指令重排序",比可见性难理解一点。

先说说指令重排序是怎么回事。

指令重排序是啥

编译器和处理器为了让程序跑得快一点,会在不改变执行结果的前提下,调整指令的执行顺序。

打个比方。你做番茄炒蛋,正常情况下应该是:洗番茄 → 切番茄 → 打蛋 → 炒菜。但如果你先打蛋再洗番茄,结果是一样的,没有人会在意你先做哪一步。编译器和处理器就是这个逻辑。

但这里有一个重要的前提:重排序不能影响单线程的运行结果

所以 CPU 在做重排序的时候,它会盯着一个原则——数据依赖性。什么意思?就是如果两个操作之间有依赖关系,那就不能重排序。

我举个例子:

操作 A:x = 1 操作 B:y = 2 操作 C:z = x + y

操作 C 要用到 x 和 y 的值,所以它必须在 A 和 B 之后执行,这个顺序不会被打破。但是操作 A 和操作 B 之间没有依赖关系——谁先谁后无所谓,反正结果都是 x=1、y=2。所以编译器或者处理器完全可以把顺序调成 B 先执行、A 后执行。

这在单线程下没问题。但是在多线程环境下就不一定了——如果另一个线程在你改 x 和 y 的中间插了一脚,看到了一个"中间状态",可能就出问题了。

volatile 怎么管这件事

volatile 是靠"内存屏障"来管这件事的。

内存屏障这个东西,你可以把它理解成一道栅栏。编译器和处理器再怎么重排序,也不能把指令从栅栏的一边移到另一边去。

Java 针对 volatile 变量,在几个关键位置插了这些栅栏:

  • 写之前插一道:保证前面的普通写操作不会被排到 volatile 写之后
  • 写之后插一道:保证 volatile 写不会被排到后面的 volatile 读/写之后。这道屏障开销最大
  • 读之后插两道:保证 volatile 读不会被排到后面的普通读和普通写之后

说实话,记这些屏障的名字(StoreStore、StoreLoad、LoadLoad、LoadStore)对我来说意义不大。我更关心的是结果:加了 volatile,代码在多线程下就不会因为指令重排序而出一些莫名其妙的问题

学完还是有点模糊的地方

写这篇的时候我又确认了一下自己的理解,发现有几个地方还是模棱两可的。

比如 volatile 和 synchronized 的区别。我知道 volatile 不能保证原子性——也就是说,如果你对一个 volatile 变量做 i++ 操作(读-改-写三步),它还是可能出问题。但具体到代码里什么时候用 volatile 什么时候用 synchronized,我还需要再多写几个例子才能有感觉。

还有一个让我纠结的点:volatile 的可见性。我看资料说它保证可见性,那是不是加了 volatile 就能保证线程安全了?不是的——刚才说了,它不保证原子性。这两个概念放在一起容易搞混,我也花了好一会儿才理清楚。

不过我觉得这就是学习的过程吧。先有个大概的框架,知道 volatile 能做什么、不能做什么,后面再慢慢往里面填细节。

简单总结一下我自己的理解

volatile 做两件事,不做第三件事:

  • 保证可见性:你改了,别人知道
  • 禁止重排序:不会乱调执行顺序
  • 不保证原子性:i++ 该出问题还是出问题

就这样。没什么花里胡哨的,记住它干什么不干什么,写代码的时候心里就有数了。


这篇是边学边写的。如果有理解不对的地方,欢迎指出来,我改。

http://www.jsqmd.com/news/1098863/

相关文章:

  • 如何免费使用Outfit字体:9种字重打造专业品牌设计的完整指南
  • 别再傻傻手写了!Python一行代码判断是不是数字,爽到飞起
  • Ansible自动化运维实战:从入门到精通,轻松管理服务器集群
  • JVM 运行时数据区 —— 5 大块内存
  • C++ Primer Plus 重读精讲 _ 指针进阶全集:三类const指针辨析、指针数组数组指针硬核区分、指针地址传参、工控函数双向改参实战
  • VMware虚拟机组网通信全链路解析(ESXi 7.0+vSphere 8.0实测验证)
  • 面向对象——多态
  • Focus架构:视觉语言模型的高效加速方案
  • 群辉Nas部署GitServer随笔
  • 别再被Python列表拷贝坑了!浅拷贝深拷贝,一个不注意就让你代码崩盘
  • 如何快速查找 *Bash* 命令的*类型*?
  • File和IO
  • 与你的 Elasticsearch 数据对话:使用 Google ADK 和 MCP 构建一个实时语音 agent ,分为 3 个组件
  • 5分钟快速上手:RedisDesktopManager-Windows终极可视化数据库管理工具完整指南
  • 告别串口乱码!STM32F401RCT6用Arduino框架点灯+串口打印保姆级教程
  • C#工业视觉实战:集成工业相机与YOLOv8实现缺陷检测系统
  • 探索兴趣爱好的内涵
  • 廖雪峰Python2教程PDF!20行代码秒杀C语言1000行,速度慢?谁在乎
  • 别再让激光器‘发烧’了!手把手教你用运放搭建高精度恒流源(附LTspice仿真文件)
  • 如何生成字母或数字的*序列*?
  • Dify平台大模型接入实战:从云端API到本地部署全流程指南
  • Postman便携版终极指南:Windows用户的免安装API开发解决方案
  • 别再只会用三极管了!用JFET搭个恒流源给LED调光,实测效果稳如老狗
  • 电脑弹窗拦截工具绿色免费超好用
  • 48.可直接落地!IEC61131-3 ST 完整源码|PLC 物料分拣 + PID 调速 + Modbus 通信
  • 零基础入门MySQL数据分析:从SQL语法到电商实战项目
  • SH9递归对抗驱动的活系统:九层架构理论体系深度研究报告(世毫九实验室原创研究)
  • linux中TCP通信
  • Python之rickshaw包语法、参数和实际应用案例
  • 基于PANDAS的QAbstractTableModel实现高级TableView详细解析(八、在TableView实现冻结窗口)