惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
IT之家
IT之家
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
C
Check Point Blog
T
Tailwind CSS Blog
博客园 - Franky
H
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
博客园 - 叶小钗
J
Java Code Geeks
腾讯CDC
罗磊的独立博客
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
酷 壳 – CoolShell
酷 壳 – CoolShell
I
InfoQ
B
Blog
V
Visual Studio Blog
F
Fortinet All Blogs

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
你真的理解 volatile 关键字了吗?
无所事事O_o · 2026-05-15 · via 博客园_首页

你真的理解 volatile 关键字了吗?

volatile 不只是"线程可见"。这篇文章用几个具体的 case,把它在 JMM 里的工作方式说清楚。


一、从最简单的 volatile 开始

volatile int flag = 0;

// 线程 A
flag = 1;

// 线程 B
if (flag == 1) {
    // ...
}

volatile 做了什么?

建立 happens-before:

  • 线程 A 对 flag 的写 happens-before 线程 B 对 flag 的读

还有一条容易忽略的规则:

volatile 写之前的所有普通写,在 volatile 读之后对读线程可见


二、volatile 不是"广播",而是"同步点"

一个常见的误解是:只要加了 volatile,其他线程就会自动看到变化。

这是错的。

Case 1:写了 volatile,但没读

int A = 0;
volatile int B = 0;

// 线程 A
A = 1;
B = 1;

// 线程 B
int x = A;   // 能看到 1 吗?

结论:❌ 不能保证

volatile 的 happens-before 只在"读发生时"生效。线程 B 没有读 B,没有同步点,就没有可见性保证。

volatile 是拉模型,不是推模型。


三、volatile + 普通变量的正确发布模式

Case 2:volatile 作用的传递性

线程 B 能正确读到 A 吗?

int A = 0;
volatile boolean ready = false;

// 线程 A
A = 1;
ready = true;

// 线程 B
if (ready) {
    System.out.println(A);
}

结论:✅ A 一定是 1

原因是 happens-before 的传递性:

如果 A happens-before B,B happens-before C
那么 A happens-before C

链路完整:

线程 a:
A = 1
  ↓  (线程内顺序)
B = 1  (volatile 写)
  ↓  (volatile 写-读)
线程 b:
if (B == 1)
  ↓  (传递性)
读取 A == 1

这是 JMM 官方认可的发布-订阅模式:

// producer
data = ...; // 普通写
ready = true; // volatile 写

// consumer
if (ready) { // volatile 读
    use(data); // 安全
}

四、多个 volatile 变量 ≠ 自动传递

Case 3:A 普通,B / C 是 volatile

int A = 0;
volatile int B = 0;
volatile int C = 0;

// 线程 A
A = 1;
B = 1;

// 线程 B
int c = C;
int a = A;

结论:❌ 不能保证看到 A = 1

volatile 的 happens-before 只对同一个 volatile 变量生效:

  • 写 B → 读 B:有保证
  • 写 B → 读 C:没有任何语义关系

volatile 不会跨变量广播内存可见性。


五、对象字段里有 volatile,就能"顺带同步"吗?

Case 4:对象里 ABC,只有 C 是 volatile

class Obj {
    int A;
    int B;
    volatile int C;
}

// 线程 A
obj.A = 1;
obj.B = 2;
obj.C = 3;

// 线程 B
int a = obj.A;
int b = obj.B;

结论:❌ 不能保证

volatile 是变量级别的,不是对象级别的。不读 C,就不会触发 volatile 读,A / B 仍然是普通读。


六、"我读了 volatile,但没用它",算吗?

Case 5:返回 volatile,但返回值没用

volatile int C;

int foo() {
    return C;
}

// 线程 B
foo();  // 返回值未使用

结论:⚠️ 不一定算

语义上确实是一次 volatile 读,但如果返回值没被使用,JIT 有权把这次读整个消掉。

两个层面分开看:

① Java 语言语义层面

return C; 在规范里就是一条 volatile 读指令,没有争议。

② JVM / JIT 实际执行层面

JMM 允许 JVM 这么做:

如果一个 volatile 读的结果对程序行为没有任何影响,JVM 可以消除这个读。

这个返回值没有赋给变量、没有用于条件判断、没有参与计算,对可观察行为没有影响,JVM 会把它当死代码处理。

需要澄清一点:

volatile 的内存语义,不是 Java 语言层面的可观察副作用。

volatile 只约束重排序与可见性,不算 I/O、不算异常、不算锁,除非它被实际执行,否则不是 happens-before 的强制保留点。


七、什么时候 volatile 读一定成立?

Case 6:volatile 参与了判断

volatile int C;

boolean foo() {
    return C == 0;
}

结论:✅ 一定是 volatile 读,不可消除

return C == 0; 和上面的 return C; 区别在于:这里读取 C 的结果直接参与了比较,影响了方法返回值。

方法返回值是可观察行为,JVM 没有任何空间把它优化掉。


八、volatile 的本质

可以把 volatile 理解为:

  • 写 volatile = release
  • 读 volatile = acquire

它同步的不是"某个变量",而是一个时间点:

在这个点之前,数据已经写完了


九、volatile 在 Disruptor 中的应用

用 Disruptor 来验证一下这个模型。

很多人用 Disruptor 时有这个疑惑:Event 字段没有 volatile,生产者写完数据,消费者却总能看到最新值。

原因是:Disruptor 同步的不是数据,而是"数据写完"这件事。

核心结构

Event 普通字段
     ↑
Sequence(volatile)

生产者逻辑(简化)

Event e = ringBuffer.get(seq);
e.x = ...;
e.y = ...;
sequence.set(seq); // volatile 写(发布)

消费者逻辑

long s = sequence.get(); // volatile 读
Event e = ringBuffer.get(s);

happens-before 链

写 Event 字段
  ↓
volatile 写 sequence
  ↓ happens-before
volatile 读 sequence
  ↓
读 Event 字段

和前面的发布-订阅模式一模一样。

Event 字段不需要 volatile,因为它不是并发访问,而是被发布的快照。一个 event slot 只被一个生产者写,之后只被一个消费者读,靠 Sequence 做交接。这是所有权转移,不是共享访问。

如果 Event 字段都是 volatile:每个字段访问都有内存屏障,cache line 来回抖,false sharing 风险暴涨。

Disruptor 用一次 volatile 发布整个对象,相当于一次内存批量提交,比字段粒度的 volatile 快得多。


十、小结

  1. volatile 不是"自动可见",而是同步点
  2. 不读 volatile,就没有 happens-before
  3. volatile 不会跨变量传播可见性
  4. 可能被优化掉的 volatile 读,不是同步点
  5. 高性能无锁结构,都是"数据 + volatile 发布点"

写在最后

volatile 在 JMM 里的行为比大多数人理解的要复杂。不是"加了就有可见性",而是"读的那一刻建立了同步"。把这个模型理解清楚,写并发代码时才能真正知道自己在保证什么。