首页 > 一个关于Java volatile关键字可见性和原子性的问题

一个关于Java volatile关键字可见性和原子性的问题

一个变量arg用volatile修饰后,会直接保存在主存中
有两个线程A和B访问它

A和B同时将arg读取到了工作内存
若A对arg进行修改后,会导致B工作内存中的arg缓存无效
所以线程B需要再次从主存中读取arg
这就保证了线程B读取的是arg的最新值

问题:
线程A和线程B都对arg变量进行++操作

++操作的过程为,先读取arg,对arg加1,然后写回主存

假设:
arg初始值为0
线程A读取了arg,阻塞
线程B读取arg,对其++,并写回主存,此时arg=1
根据可见性,线程A工作内存中的arg变量应该会失效
此时线程A需要重新从主存中读取arg=1,然后进行++操作,将结果2写回内存

然而:
看了一些博客,都没有涉及到加粗文字的步骤。

按照这样的说法,arg会被写两次,每次都是1。
线程A读取arg放入工作内存后,线程B的写操作不会影响线程A工作内存中arg变量的缓存。

问:这时候可见性不发挥作用么?


volatile保证你每次读取都能读到最新的值,可是并不会更新你已经读了的值,它也无法更新你已经读了的值。


  1. 首先,使用volatile,工作内存的概念依然存在,只是会通过读写屏障来达到直接读写内存的效果。所以,你可以认为:volatile变量直接操作内存(虽然严格说并不是)

  2. 读值是指读到cpu,后cpu对值++,然后重新写回内存(或工作内存),读到的值指的是读到CPU的值,这个是没法因为内存值的改变而自动改变的。


没有什么工作内存、主内存,对编译器来说内存和Cache的区别是不可见的,编译器关心的是内存和寄存器,如果你非要把寄存器叫做工作内存也不是不可以但是很别扭……volatile影响的是编译器在连续的多个语句之间是否可以假设这个变量没有被别人修改,这样就可以继续用之前在寄存器里面的值。否则编译器总是去内存里读一个新的值出来,而不使用寄存器里面暂存的值。它也不关心这其中有没有别人实际修改了这个值。至于多核系统内存和Cache之间的关系,在x86架构上这是CPU自己会处理的问题,在一些别的架构上可能会设计一些特殊的指令。
也就是说根本就没有你说的一个修改导致另一个失效那样子的魔法过程。如果另一个线程是在++的执行过程中,刚读出来的值被修改了,它当然是没有办法用超能力去预知这个事情然后重新读一次的。


可见性的解释应该是保证多个线程对该变量(内存中的某个区域)的“读”是最新。

http://gee.cs.oswego.edu/dl/c...

Declaring a field as volatile differs only in that no locking is involved. In particular, composite read/write operations such as the "++'' operation on volatile variables are not performed atomically.

所以在这种组合读写的场景下,可见性只是不能保证最后的结果,但是它确实在发挥作用。

Using volatile fields can make sense when it is somehow known that only one thread can change a field, but many other threads are allowed to read it at any time.

这也是大神推荐的使用场景。

另外,我们可以反过来考虑,
假设初始x=0,t1和t2均读取到x=0,接下来t1设置x=1之后,
如果t2对x设置值=1,
问1.系统为什么会认为这个操作失效?
问2.失效后怎么处理?

  1. t2在设置x的时候,系统会自动把x=0带上去比较之前x的值?

    CAS,除非java把此类的变量都编译成带CAS操作的字节码。
    
  2. 假设也存在以上这一的机制,t2执行x=1失效之后会有什么操作?

    抛异常?程序捕获然后又重新读取设置?又回到CAS上面了。
    

希望对题主有所帮助,如有遗漏欢迎指正。

【热门文章】
【热门文章】