浅谈Java并发编程系列(三)—— volatile型变量

当一个变量定义为volatile以后,它具有两种特性:java

  1. 保证此变量对全部线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其余线程来讲是能够当即得知的。缓存

  2. 禁止指令重排序优化。工具

在X86处理器下经过工具获取 JIT编译器生成的汇编指令来看下volatile变量进行读写操做时CPU的行为:
Java 代码以下:性能

// volatile Object instance;
instance = new Singleton();

生成的汇编代码以下:优化

0x01a3de1d: movb $0X0, 0X1104800(%esi); 0X01a3de24: lock addl $0X0, (%esp);

有volatile变量修饰的共享变量进行写操做的时候会多出第二行汇编代码,Lock前缀的指令在多核处理器下会引起了两件事件。线程

  1. 将当前处理器缓存的数据写回到系统内存。3d

  2. 这个写回内存的操做会使在其余CPU里缓存了该内存地址的数据无效。code

再让咱们从Java内存模型的角度分析下volatile变量。假定T表示一个线程,V和W分别表示两个volatile变量,那么在进行read, load, use, assign, store和write时须要知足如下三条规则:排序

  1. 只有当线程T对变量V执行的前一个动做是load时,T才能对V执行use; 而且,只有当T对V执行的后一个动做是use时,T才能对V执行load。T对V的use动做能够认为是和线程T对V的load,read动做相关联,必须连续一块儿出现(这条规则要求 在工做内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其余线程对变量V所作的修改后的值)。事件

  2. 只有当线程T对变量V执行的前一个动做是assign时,T才能对V执行store动做;而且,只有当T对变量V执行的后一个动做是store时,线程T才能对变量V执行assign动做。线程T对变量V的assign动做可认为是和线程T对变量V的store, write动做相关联,必须连续一块儿出现(这条规则要求 在工做内存中,每次修改V后都必须马上同步回主内存中,用于保证其余线程能够看到本身对变量V所作的修改)。

  3. 假定动做A是线程T对变量V实施的use或assign操做,假定动做F是和动做A相关联的load或store动做,假定动做P是和动做F相应的变量V的read或write动做;相似的,假定动做B是线程T对变量W实施的use或assign动做,假定动做G是和动做B相关联的load或store动做,假定动做Q是和动做G相应的变量W的read或write动做。若是A先于B,那P先于Q(这条规则要求 volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同)。

因为volatile变量只能保证可见性,在不符合如下两条规则的运算场景中,仍然要经过加锁(使用synchronized或java.util.concurrent中的原子类)来保证原子性:

  1. 运行结果并不依赖变量的当前值,或者可以确保只有单一的线程修改变量的值。

  2. 变量不须要与其余的状态变量共同参与不变约束。

在某些状况下,volatile的同步机制性要优于锁。而且,volatile变量读操做的性能消耗与普通变量几乎没有什么差异,可是写操做则可能会慢一些,由于它须要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

参考:
深刻分析Volatile的实现原理

相关文章
相关标签/搜索