Volatile总结

本文探讨了Java中volatile关键字如何解决多线程环境下的内存可见性问题,通过内存屏障技术保证了数据的一致性和处理器间正确通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

volatile被视作是轻量级的sychronized。与sychronied关键字比较,volatile只能保证共享变量数据的可见性,即,当一个变量被多个线程共享,可修改时,一个线程的修改结果会立刻对其他线程可见。

volatile是如何做到可见性的?

首先,要明白为什么会有可见性问题

CPU负责数据处理,实际的运行时数据存储在内存中,CPU和内存之间通过总线传递数据。由于CPU和内存对数据处理的速度有很大差异,所以通常CPU存取的数据都不会直接与内存交互,转而经过处理器缓存读写。这样的直接后果是,处理器缓存内的数据和内存中的数据未必一致,无法保证最新被处理过的结果立刻刷新到内存中去,同样也无法保证读取到的就是最新被刷新到内存的结果,可见性问题由此产生。类似于这种描述,Java在线程通信方面,采用的是共享内存模型,线程通过共享状态,隐式通信。从下图看,线程A、B之间的通信,需要先把本线程内的共享变量副本刷新到主存,然后经由另一个线程再去将主内存中的变量值同步到该线程本地内存中。

此外,处理器的执行顺序和内存中的顺序不会一致,虽然从性能优化的角度考量,这是很合理的,但是会对程序员编程造成一定的影响。如何保证内存编程的结果和想要的一致,需要介于程序员和处理器之间建立约定。

上图引用自《Java并发编程艺术》一书,很好的阐释了程序员与处理器之间的矛盾,程序员希望很好的控制程序运行,程序按定义语义执行,处理器则希望尽可能的提高效率,尽量不受执行顺序约束。为了应对二者之间的矛盾,需要给上层的程序编写者提供一个很强的保证,即按照该保证编程,就能得到想要的执行结果(在此并不能完全承诺执行顺序和保证的顺序一致)。这个“保证”就是“happens-before”规则。

它承诺:

(1)程序顺序:单线程内的每个操作,happens before该线程内的后续操作;

(2)锁:解锁操作,happens before随后对这个锁的加锁;

(3)volatile:volatile域的写,happens before后续对这个域的读;

(4)传递性:a happens before b,b happens before c,则有a happens before c。

程序员只要按照这个原则编程,就可以保证处理器执行结果。

那么volatile是如何实现happens before的效果?也就是有效阻止某些处理器的重排序,答案是内存屏障(P26)。一旦域被volatile修饰,编译后的字节码内会被添加上各种内存屏障。同样加上其他,如synchronized和final原语也会添加这种屏障。JMM把内存屏障分为四类,即LoadLoad、StoreStore、LoadStore和StoreLoad屏障,其中StoreLoad具有其他三种的所有效果,被现代处理器广泛支持。

StoreLoad Barriers指令示例为Store1;StoreLoad;Load2,确保Store1数据对其他处理器变得可见(即刷新到内存)先于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。

关于volatile实现原理,可以参考《Java并发编程艺术》P39-47。文章主要阐述的就是,写-读的内存语义,以及volatile是如何针对这种场景添加内存屏障,保证程序员看到的“happens before”原则视图有效的。复习时可以看看,找找感觉。

 

转载于:https://www.cnblogs.com/jenkov/p/volatile_review.html

### Java `volatile` 关键字与内存屏障的有序性总结 #### 1. **什么是内存屏障** 内存屏障是一种同步机制,用于确保某些操作在特定顺序下完成。它是一个硬件级别的指令,在多核处理器环境中防止编译器优化或 CPU 的乱序执行行为。通过插入内存屏障,可以强制使屏障前后的读写操作按照指定顺序发生[^1]。 #### 2. **Volatile 和 内存屏障的关系** 当变量被声明为 `volatile` 后,Java 编译器会在对该变量进行读写操作时自动插入相应的内存屏障。具体来说: - 对于 `volatile` 变量的写入操作,会触发一个释放屏障 (Release Barrier),这确保在此之前的任何写操作都会先于该 `volatile` 写操作完成。 - 对于 `volatile` 变量的读取操作,则会触发一个获取屏障 (Acquire Barrier),从而保证此之后的任何读操作都不会提前到这个 `volatile` 读操作之前[^4]。 #### 3. **代码示例分析** 以下是基于提供的代码片段来说明 `volatile` 如何利用内存屏障保障有序性的: ```java public class MemoryBarrierExample { private int num; private volatile boolean ready; public void actor2(I_Result r) { num = 2; // 普通写操作 ready = true; // Volatile 写操作,带有 Release Barrier // 所有在此之前的操作(num=2)都被提交并可见给其他线程 } public void actor1(I_Result r) { if (ready) { // Volatile 读操作,带有 Acquire Barrier // 防止后续读取 num 提前至本语句之前 r.r1 = num + num; // 安全依赖于前面的条件判断 } else { r.r1 = 1; } } } ``` 在这个例子中: - 当线程调用 `actor2()` 方法并将 `ready` 设置为 `true` 时,由于这是一个 `volatile` 写操作,因此 JVM 自动在其前后插入了一个释放屏障。这意味着所有在线程 A 中发生在设置 `ready=true` 之前的修改(如 `num=2`),都将对其他线程变得可见。 - 在另一个线程中,如果检测到了 `ready==true` (即发生了 `volatile` 读操作),则因为存在获取屏障,所以能够确信此时看到的是最新状态下的数据,并且不会出现因重排序而导致逻辑错误的情况[^2]。 #### 4. **Volatile 的局限性** 尽管 `volatile` 能够很好地提供内存可见性和一定程度上的有序性支持,但它并不能替代锁 (`synchronized`) 来处理复杂的并发场景。特别是对于复合动作或者涉及多个共享资源的状态更新而言,仅靠 `volatile` 并不足以满足原子性需求[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值