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

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

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


一、从最简单的 volatile 开始

java 复制代码
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,但没读

java 复制代码
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 吗?

java 复制代码
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 官方认可的发布-订阅模式:

java 复制代码
// producer
data = ...; // 普通写
ready = true; // volatile 写

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

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

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

java 复制代码
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

java 复制代码
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,但返回值没用

java 复制代码
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 参与了判断

java 复制代码
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)

生产者逻辑(简化)

java 复制代码
Event e = ringBuffer.get(seq);
e.x = ...;
e.y = ...;
sequence.set(seq); // volatile 写(发布)

消费者逻辑

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

相关推荐
北冥湖畔的燕雀1 小时前
C++日志系统:从原理到实战实现
java·开发语言
java修仙传1 小时前
Java 实习日记:一次 Excel 导入校验 Bug 的定位与数据更新逻辑优化
java·数据库·bug·excel·后端开发
稽稽稽稽不如人1 小时前
《从零开始的java从入门到入土的学习生活——Java后端篇》Chapter21——Java后端篇学习记录——Redis初步入门
java·学习·生活
ID_180079054731 小时前
淘宝店铺所有商品 API 接口:核心能力与数据返回参考
java·服务器·前端
轻刀快马1 小时前
浅聊Java反射
java·开发语言
Gerardisite1 小时前
企业微信智能客服开发实战:API自动回复指南
java·开发语言·python·机器人·企业微信
要开心吖ZSH1 小时前
零基础入门 Spring WebFlux 与 Project Reactor:从小白到顿悟
java·响应式编程·spring webflux
智塑未来1 小时前
装备制造行业设计制造一体化痛点攻克与实战经验总结
java·开发语言·制造
Devin~Y1 小时前
电商AIGC智能客服面试:JVM调优、Spring Cloud微服务、Redis缓存、Kafka消息、K8s观测与RAG落地
java·jvm·spring boot·redis·spring cloud·kafka·kubernetes