volatile关键字浅析
来源:互联网 发布:手机淘宝电脑版在哪里 编辑:程序博客网 时间:2024/06/06 20:25
volatile关键字浅析
在并发编程中,volatile是很常用的一个修饰符。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在某些用途下比锁更方便,一个变量被声明为volatile,jvm能够保证所有的线程都看到的是一致的变量,也就说一个线程对一个共享变量修改,其他线程能够立即看到最新的值。
volatile在某些情况下可以代替锁,比synchronized成本和开销更低,因为不会引起线程的上下文切换,为什么上下文切换就会开销比较大,因为原来缓存的指令和数据都没用了,要重新加载指令和数据到缓存中,你说开销大不大。
1.首先看看可见性
volatile修饰的共享变量,当该变量发生写操作时,把该变量刷新到内存中,会使所有的CPU缓存的该共享变量失效。volatile修饰的变量写操作,在x86处理器下JIT编译器生成的汇编指令大致如下:
mov %r8d,0xc(%r10) ; Store lock addl $0x0,(%rsp) ; StoreLoad Barrier
这里会多出一个lock指令,lock指令会把当前CPU缓存的该行写到内存中,同时使其他CPU的缓存失效。现代多处理器体系中,为了加快执行速度,都会使用缓存(cache),数据载入到缓存中再进行各种操作。lock指令把这个变量所在的缓存行回写到内存中,多处理器缓存之间有一致性协议,这时候发现内存中的值已经修改,其他处理器的缓存会被置为失效,这样就达到了在多CPU之间可见。这里插一句,以前lock实现是通过锁总线,现在是通过锁缓存区域,因此开销降低了很多。
2.再来看看重排
学过计算机体系结构和编译原理都知道,现代计算机都是采用流水线结构,编译器会在不影响执行结果的情况下把指令执行顺序优化,最大程度地发挥计算机各个部件的并行特性。再看上面的汇编指令,lock addl $0x0,(%rsp)这是内存屏障(memory barrier),它除了刷新所有cpu缓存外,还会告诉CPU,先于这个命令的必须先执行,后于这个的必须后执行,确保操作执行的顺序,限制了指令重排。
有了这个,才会有happens-before规则,比如以下典型的happens-before例子;
int a = 0;volatile boolean flag = false;public void writer() { a = 1; //1 flag = true; //2}public void reader() { if(flag){ //3 int i = a; //4 }}
假设线程a调用了writer()方法,由于flag是volatile修饰的,禁止了编译器重排,步骤1一定会先于步骤2执行。在reader方法中,如果flag是true,那么这个时候a的值一定是1。当然,happens-before还有很多其他的效果,这里只是一个简单例子。
3.原子性
java中,64位机器上给所有基本类型变量赋值操作都是原子的,但是在32位机器上,long和double类型赋值并不是。long和double类型是8字节,32位机器上赋值是先赋值32位,后赋值32位,中间可能被中断,因此不是原子的。如果把long和double类型用volatile来修饰,即可保证赋值操作是原子的了,根据前面的分析,其实很好理解为什么加了volatile之后赋值就是原子的了,因为jvm编译器会加一个lock前缀的指令,计算机会把对应的缓存区域锁住,其他线程就无法修改了,这样就完成了原子性赋值。
在java中,以下操作是原子的:
all assignments of primitive types except for long and doubleall assignments of referencesall operations of java.concurrent.Atomic* classesall assignments to volatile longs and doubles
volatile修饰的变量仅仅能保证简单的赋值和读取的原子性,并不能保证复杂操作的原子性,如下:
private volatile int i=0; ... f(){ i++; ... }
i++这个并不具有原子性,因为i++会转变成好几条指令执行,1)读取当前的值,2)自增,3)存储。这些指令之间随时都有可能被中断,是不安全的。
以上为volatile的简单理解。
- volatile关键字浅析
- 浅析volatile关键字
- 浅析volatile关键字
- volatile 关键字浅析
- Java中volatile关键字浅析
- java中volatile关键字浅析
- C关键字 register extern const static volatile typedef 逐一浅析
- volatile浅析
- volatile浅析
- volatile关键字
- 关键字volatile
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- volatile关键字
- AndroidStudio的使用技巧
- 详解原型链继承
- Java中无返回值的return语句使用总结
- 自考、起航!
- Leetcode #8 String to Integer (atoi)
- volatile关键字浅析
- 解决Windows下Python程序读取中文文件
- JMS简介与ActiveMQ实战
- [Win32]一个调试器的实现(四)读取寄存器和内存
- 关于ajax
- [知识点滴]HTML5-布局总结
- 逻辑操作需要注意的问题
- String转int
- Spark源码学习——在Linux环境下使用IDEA看Spark源码