被问到Volatile,讲不清楚,回来好好补课。
几个相关的知识要理解:多线程,内存模型,乱序优化,锁,原子性,可见性
如果不定义为volatile,会发生什么
主内存中的数据,例如var,会被缓存在寄存器中,进行read/write操作之后,在某个时间复制回内存。在多核CPU上运行多线程的程序,共享变量会因为多个线程,产生多个寄存器中的缓存,在一个线程中的修改,被写回内存之前,其他线程是看不到的。
volatile的效果
一个共享变量var定义为volatile,那么,如果两个线程,a线程write,b线程在之后read,那么前者的操作结果一定会被后者读到,也即:b线程读到的一定是a线程write之后的结果(假设中间没有其他操作)
ref: http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
根据Java语言规范,volatile保证了共享变量在多线程下的“可见性”
volatile如何保证可见性
编译器遇到volatile时,就会要求CPU不在寄存器中进行缓存,而是直接操作内存。
要更好地理解可见性,可以阅读Java语言规范的内存模型一节:http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4
原子性
在Java Tutorial中,对原子操作是这样描述的:
- Reads and writes are atomic for reference variables and for most primitive variables (all types except
long
anddouble
). - Reads and writes are atomic for all variables declared
volatile
(includinglong
anddouble
variables)
所以,第二句话中的all variables,我只能理解为primitive variables
实现复杂操作的原子性
还是离不开锁
两种锁,悲观锁,乐观锁
Synchronize属于悲观锁,用得很多了,不需赘述
乐观锁,在这里一般就指 CAS - Compare and Swap,不加锁,每次write操作之前,检查操作目标的当前状态是否和本线程中缓存的状态一致,如果不一致,则重新读取,重新尝试write操作。
阅读java.util.concurrent.atomic包中的源代码,可以看到大量调用了 sun.misc.Unsafe.compareAndSwapXxx()
Compare和Swap是两个动作,如何保障这两个动作的原子性,也即这两次操作期间没有其他线程修改操作目标?这就需要阅读sun.misc.Unsafe的源码,进行更深入的研究了。根据网上看到的资料,这部分是JNI方法。还没有去找这部分代码来读,以后有机会再继续学习。
下面这个网页是对 sun.misc.Unsafe 的一个介绍:http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
需要说的是,用Volatile和CAS,性能未必比synchronize/Lock好。需要具体情况具体分析。
网上关于应用Volatile的推荐原则如下:
(1)写入变量不依赖此变量的值,或者只有一个线程修改此变量
(2)变量的状态不需要与其它变量共同参与不变约束
(3)访问变量不需要加锁
小结
1. Java volatile 提供操作对象在多线程之间的可见性,volatile变量不会在寄存器中缓存,只保存在内存中
2. volatile本身不提供原子性,但可以结合 CAS 的方式,实现非阻塞的并行计算
3. 因为Java规范保证对原始数据类型操作的原子性,因此,对此类数据的访问,可以利用volatile实现不加锁的同步访问,但其性能未必是最优的
扩展阅读: