对Volatile变量的理解和总结

之前因为对Volatile变量的理解不够深刻,只是知道它可以轻量级的锁,但是在真正编写多线程的程序时,除用它来做为线程结束的标志外,并不敢过多的使用。但是在看Current包中的大部分源码时发现,作者居然使用Volatile变量加CAS操作实现了很多只有通过加锁才能实现的功能,叹为观止。然后就在前面了解了JMM的基础上,又去详细的了解了一下Volatile变量的使用。

volatile变量的读写对所有线程立即可见。

只是读和写一步,复杂的运算不能保证对其他线程可见,因为复杂的运算可能会被编译成多条指令,JMM只保证,volatile变量从工作内存写回到主存是对其他线程可见的。先看一个具体的例子。

static volatile int i = 0;

    // -XX:+PrintGC
    // -XX:+PrintGCDetails
    // -Xms20m
    // -Xmn10m
    // -Xmx20m
    // -XX:+UseSerialGC
    // -XX:MaxTenuringThreshold=15
    // -XX:-HandlePromotionFailure
    // -XX:+PrintHeapAtGC
    public static void main(String[] args) {
        int a = i++;
    }

编译后的指令 

这里写图片描述 
JMM只是能够保证(并不一定能够保证,但是一条字节码的指令也是由若干机器指令完成的,但是能够说明问题了)getstatic 和 putstatic volatile变量的时候是原子的,至于中间的一些列操作,并不能够保证再次期间没有其他线程对i操作生成脏数据。也就是,JMM保证get操作的值是当前内存中最新的,以及put之后内存中i的对其他内存可见。

禁止指令的重排序

这一点,《java并发编程的艺术》一书中讲的比较详细。 
重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义,JMM会分别限制这两种类型的重排序类型。JMM针对编译器制定的volatile重排序规则见下表。 
这里写图片描述

个人总结来说,

  • volatile的读下面的任何操作都不能重排序到volatile读操作的上方,volatile上面的普通读写可重排序到下方。
  • volatile的写上面的任何操作都不能重排序到volatile写操作的下方,volatile下面的普通读写可重排序到上方。
  • 任何两个volatile的读写顺序不能重排。

为了实现上述的volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。(StoreStore等屏障的介绍见文章最后)

  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadStore屏障。

插入屏障后的效果见下图 
这里写图片描述

LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。

这里写图片描述

StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。 
这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在采取了保守策略:在每个volatile写的后面,或者在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。

上面这段话引自《java并发编程的艺术》一书,但是不是很明白,volatile的写前面的所有操作都不得拍到volatile写之后,为什么这里只加入了Store-Store屏障呢,这样普通读不就可以重拍到volatile写的下方了??????

如果,volatile读的上面还有volatile读,因为volatile读下面都会插入load-load屏障,所以两者不会重排。如果volatile读的上面还有volatile写,volatile写后面加入了store-load,所以下面的volatile读不能能与之重排序。

屏障介绍: 
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值