volatile关键字特性
- 多CPU环境的可见性: 多CPU的环境下, CPU有可能从寄存器或Cache里直接取值, 这种情况下运行在不同CPU上的线程获取的值可能不同, volitile变量可以保证每次更新都改变到主存, 每次读取都从主存中读取.
- volatile 可以作为一种开销较低的免锁机制(某些情况下).
- volatile 的
long
,double
的读写不保证有原子性. - volatile变量的”复合操作”(对变量的写操作依赖当前值)不具备原子性.
volatile 的应用
➤ 适用的情况:
- 作为简单的状态标志,
vol_variable = 1
和vol_variable = 0
这种操作是原子的, 对 volatile 变量的赋值也对其他线程立刻可见; - 保证只有一个线程写, 其他线程只能读;
➤ 不适用的情况:
- 用于计数器(请使用
AomicInteger
), 虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由 “读取-修改-写入” 操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。 - “依赖当前值”的写操作, 比如
i=i+1
- 非原子操作,
i++
,i=!i
都不是原子操作
比如以下代码是有问题的:
private volatile i = 0; |
Volatile 的特性是如何实现的?
- 第一:使用 volatile 关键字会强制将修改的值立即写入主存;
- 第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程1的工作内存中缓存变量的 缓存行 无效(缓存行,CPU 高速缓存中的 cache line)
- 第三:由于线程1的工作内存中缓存变量的 缓存行 无效,所以线程 1 再次读取变量的值时会去主存读取。
上面的特性,是通过 内存屏障 实现的,详见 Java-并发.09.深入理解JMM
Volatile Double 是原子的吗?
先看硬件的支持,如果要保证 double 写的原子性,那么要满足 64bit + 内存对齐 + 它没跨 cache line
- 如果是 64bit,且这个 double 是内存对齐的,那么对它的读写是原子的,但是很多情况下(C++ 和 Java 中)无法保证对齐,也就无法保证原子性
- 根据 IA64 手册,X86_64 架构下,不跨越 cacheline 的 8byte 读写是原子的
Double 是否是原子性,1 看硬件(64),2 看上层是否实现了对 double 的对齐
Java 语言规范文档:jls-17 Non-Atomic Treatment of double and long
- 对于 64 位的 long 和 double,如果没有被 volatile 修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对 32位操作。
- 如果使用 volatile 修饰 long 和 double,那么其读写都是原子操作
- 在实现 JVM 时,可以自由选择是否把读写 long 和 double 作为原子操作
作者测试了 32 Hotspot 和 64 下多线程写 volatile double 的表现:
- 32:无法保证原子性
- 64:看结果是可以保证