Java中的易失性变量

时间:2011-06-07 01:17:32

标签: java multithreading concurrency volatile

所以我正在阅读这本名为 Java Concurrency in Practice 的书,我仍然坚持这个解释,如果没有一个例子我似乎无法理解。这是引用:

  

当线程A写入volatile时   变量和随后的线程B   读取相同的变量,值   可见的所有变量   在写入volatile之前A   变量在B之后变得可见   读取易变量。

有人可以给我一个反例,说明为什么“在写入volatile变量之前A可见的所有变量的值在读取volatile变量后对B可见”?

我很困惑为什么在读取volatile变量之前所有其他非易失性变量都不会被B看到?

5 个答案:

答案 0 :(得分:25)

声明一个易变的Java变量意味着:

  • 此变量的值永远不会在本地线程缓存:所有读取和写入都将直接进入“主内存”。
  • 对变量的访问就像包含在同步块中一样,自身同步。

仅供参考,何时需要挥发性?

  

当多个线程使用相同时   变量,每个线程都会有它的   拥有本地缓存​​的副本   变量。所以,当它正在更新时   值,它实际上是在更新   本地缓存不在主变量中   记忆。另一个线程是   使用相同的变量不知道   关于价值的任何改变   另一个线程。为了避免这种情况   问题,如果你声明一个变量   不稳定,然后就不会存储   在本地缓存中。每当线程   正在更新值,它会更新   到主存。所以,其他线程   可以访问更新的值。

来自 JLS §17.4.7 精心制作的执行

  

我们只考虑结构良好   处决。执行E =< P,A,   po,so,W,V,sw,hb>形成得很好   如果满足以下条件:

     
      
  1. 每次读取都会看到写入相同的内容   执行中的变量。所有读物   并写入volatile变量   动荡的行动。对于所有读取r in   A,我们在A和W(r)中有W(r).v = r.v.   如果和,变量r.v是易失性的   只有当r是易失性读数时,才有   变量w.v当前是唯一的   如果w是易失性写入。

  2.   
  3. 发生前 - 订单是部分的   订购。发生在订单之前   通过传递关闭   与边缘和程序同步   订购。它必须是有效的部分   顺序:反身,传递和   反对称。

  4.   
  5. 执行服从   线程内一致性。对于每一个   线程t,由t执行的动作   在A中是相同的   由该线程生成的   程序顺序与每个程序顺序隔离   写入写入值V(w),给定   每个读取r看到的值   V(W(R))。每次阅读看到的值都是   由记忆模型决定。该   给出的程序顺序必须反映出来   程序顺序中的动作   将根据。进行   P.

  6. 的线程内语义   
  7. 执行是在一致之前发生的   (§17.4.6)。

  8.   
  9. 执行服从   同步顺序一致性。对于   所有易失性读取在A中,它不是   这样的情况(r,W(r))或   存在写赢这样的   w.v = r.v等等(W(r),w)和   所以(w,r)。

  10.   

有用的链接:What do we really know about non-blocking concurrency in Java?

答案 1 :(得分:15)

线程B可能具有这些变量的CPU本地缓存。读取volatile变量可确保观察到从先前写入volatile的任何中间缓存刷新。

例如,请阅读以下链接,其中以“使用易失性修复双重锁定”结束:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

答案 2 :(得分:6)

如果变量是非易失性的,那么编译器和CPU可以根据需要自由地重新排序指令,以便优化性能。

如果变量现在声明为volatile,则编译器不再尝试优化对该变量的访问(读取和写入)。然而, 可以继续优化对其他变量的访问。

在运行时,当访问volatile变量时,JVM会为CPU生成适当的内存屏障指令。内存屏障具有相同的用途 - CPU也可以防止重新排序指令。

当一个volatile变量写入(通过线程A)时,所有对任何其他变量的写操作都会完成(或者至少会显示)并在写入volatile变量之前对A可见;这通常是由于内存写入屏障指令。同样,对其他变量的任何读取都将在之前完成(或将显示为) 读(通过线程B);这通常是由于内存读屏障指令。这种由屏障强制执行的指令排序将意味着A可见的所有写入都将是可见的B.但是,这并不意味着任何重新排序的指令都没有发生了(编译器可能已经对其他指令进行了重新排序);它只是意味着如果A发现任何可见的写入,它就会对B可见。简单来说,这意味着不保持严格的程序顺序。

如果您想更详细地了解JVM如何发布内存屏障指令,我将在Memory Barriers and JVM Concurrency上指出此文章。

相关问题

  1. What is a memory fence?
  2. What are some tricks that a processor does to optimize code?

答案 3 :(得分:3)

允许线程缓存自其读取以来其他线程自更新以来可能具有的变量值。 volatile关键字强制所有线程不缓存值。

答案 4 :(得分:1)

如果你使用volatile变量,这只是内存模型给你的额外奖励。

通常(即,在没有volatile变量和同步的情况下),VM可以按照它想要的任何顺序将变量从一个线程可见到其他线程,或者根本不存在。例如。阅读线程可以读取另一个线程变量赋值的早期版本的混合。这是因为线程可能在具有自己的缓存的不同CPU上运行,这些缓存有时仅被复制到“主存储器”,并且还会通过代码重新排序以进行优化。

如果你使用了一个volatile变量,一旦线程B从中读取了一些值X,VM就会确保线程A在写入X之前写入的任何内容对B也是可见的。(以及A得到的所有内容)保证可见,过渡性。)

对同步块和其他类型的锁提供了类似的保证。