Java volatile变量原子性讨论
来源:互联网 发布:软件找不到怎么卸载 编辑:程序博客网 时间:2024/06/06 05:12
JDK官方文档是这样形容volatile的:
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.
意思就是说,如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性)。
我们都知道Java volatile关键字是为了保证共享变量在多线程间的可见性,即某个线程修改了共享变量的值,其他线程可以能够立即读到这个修改后的值。可见性的实现原理可以简单的概况如下:volatile变量进行写操作时,JIT编译器会在生成的汇编指令后加上一个lock前缀的额外指令,这个lock指令会使得处理器缓存立即回写到主内存,并使得其他处理器缓存的该缓存行无效。
前面讨论了volatile变量的可见性,那么现在说说它的原子性,先看看下面的代码:
package com.company;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;/** * Created by root on 2017/10/14. */public class VolatileTest { private static volatile int anIntV = 0; private static AtomicInteger anIntA = new AtomicInteger(0); public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(4); new Thread(() -> { for (int i = 0; i < 200000; i++) { anIntV++; anIntA.getAndIncrement(); } latch.countDown(); }).start(); new Thread(() -> { for (int i = 0; i < 200000; i++) { anIntV++; anIntA.getAndIncrement(); } latch.countDown(); }).start(); new Thread(() -> { for (int i = 0; i < 200000; i++) { anIntV++; anIntA.getAndIncrement(); } latch.countDown(); }).start(); new Thread(() -> { for (int i = 0; i < 200000; i++) { anIntV++; anIntA.getAndIncrement(); } latch.countDown(); }).start(); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("anIntV = " + anIntV); System.out.println("anIntA = " + anIntA); }}
这段代码的执行结果如下
不同于anIntA每次执行结果都是800000,而且这里的anIntV每次执行的结果都不一样。我们可以先下结论,volatile变量没有保证变量的原子性。
我们回头来看看volatile变量为何不保证原子性。让一个volatile的integer自增(i++),其实要分成3步:
1)读取volatile变量值到local;
2)增加变量的值;
3)把local的值写回,让其它的线程可见。
这3步的jvm指令为:
mov 0xc(%r10),%r8d ; Loadinc %r8d ; Incrementmov %r8d,0xc(%r10) ; Storelock addl $0x0,(%rsp) ; StoreLoad Barrier
注意最后一步是内存屏障。
如果变量是volatile 变量,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。
再回来看前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。
参考
为什么volatile不能保证原子性而Atomic可以?
- Java volatile变量原子性讨论
- Java关键字volatile,原子性,变量可见性
- java并发编程实践--原子变量、volatile、synchornized
- java中volatile关键字的含义 原子类型变量使用
- volatile AtomicInteger java多线程操作 原子性
- java原子性以及关键字volatile、synchronized
- java volatile double、long的原子性
- volatile 不能保证变量的原子性的操作
- 使用Volatile变量还是原子变量
- 原子性和volatile
- Java原子变量&原子操作
- JAVA拾遗 - volatile关键字和原子性的探讨
- Java自增原子性问题(测试Volatile、AtomicInteger)
- Java自增原子性问题(测试Volatile、AtomicInteger)
- Java并发编程:volatile关键字解析(原子性问题)
- Java多线程之原子性 volatile、atomicInteger、synchronized测试
- java 原子变量AtomicInteger
- java原子变量
- java设计模式--Singleton单例模式
- SSH免密码登录Linux服务器
- 51Nod——1004 n^n的末位数字
- 1.系统管理命令ps和kill
- 000-动态规划简介
- Java volatile变量原子性讨论
- Codevs 1217 && 洛谷 P1083 借教室
- Python 进程,获取进程id( os.getpid() )
- 静态内部类的加载
- [jQuery] 把jqGrid修改为响应式表格
- Expectation-Maximum(EM算法)
- Workbench has not been created yet
- HDU-3746-Cyclic Nacklace
- 浅析AOP实现原理(1)静态代理