所以我正在阅读这本名为 Java Concurrency in Practice 的书,我仍然坚持这个解释,如果没有一个例子我似乎无法理解。这是引用:
当线程
A
写入volatile时 变量和随后的线程B
读取相同的变量,值 可见的所有变量 在写入volatile之前A
变量在B
之后变得可见 读取易变量。
有人可以给我一个反例,说明为什么“在写入volatile变量之前A
可见的所有变量的值在读取volatile变量后对B
可见”?
我很困惑为什么在读取volatile变量之前所有其他非易失性变量都不会被B
看到?
答案 0 :(得分:25)
声明一个易变的Java变量意味着:
仅供参考,何时需要挥发性?
当多个线程使用相同时 变量,每个线程都会有它的 拥有本地缓存的副本 变量。所以,当它正在更新时 值,它实际上是在更新 本地缓存不在主变量中 记忆。另一个线程是 使用相同的变量不知道 关于价值的任何改变 另一个线程。为了避免这种情况 问题,如果你声明一个变量 不稳定,然后就不会存储 在本地缓存中。每当线程 正在更新值,它会更新 到主存。所以,其他线程 可以访问更新的值。
来自 JLS §17.4.7 精心制作的执行
我们只考虑结构良好 处决。执行E =< P,A, po,so,W,V,sw,hb>形成得很好 如果满足以下条件:
每次读取都会看到写入相同的内容 执行中的变量。所有读物 并写入volatile变量 动荡的行动。对于所有读取r in A,我们在A和W(r)中有W(r).v = r.v. 如果和,变量r.v是易失性的 只有当r是易失性读数时,才有 变量w.v当前是唯一的 如果w是易失性写入。
发生前 - 订单是部分的 订购。发生在订单之前 通过传递关闭 与边缘和程序同步 订购。它必须是有效的部分 顺序:反身,传递和 反对称。
- 的线程内语义
执行服从 线程内一致性。对于每一个 线程t,由t执行的动作 在A中是相同的 由该线程生成的 程序顺序与每个程序顺序隔离 写入写入值V(w),给定 每个读取r看到的值 V(W(R))。每次阅读看到的值都是 由记忆模型决定。该 给出的程序顺序必须反映出来 程序顺序中的动作 将根据。进行 P.
执行是在一致之前发生的 (§17.4.6)。
- 醇>
执行服从 同步顺序一致性。对于 所有易失性读取在A中,它不是 这样的情况(r,W(r))或 存在写赢这样的 w.v = r.v等等(W(r),w)和 所以(w,r)。
有用的链接: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上指出此文章。
相关问题
答案 3 :(得分:3)
允许线程缓存自其读取以来其他线程自更新以来可能具有的变量值。 volatile
关键字强制所有线程不缓存值。
答案 4 :(得分:1)
如果你使用volatile变量,这只是内存模型给你的额外奖励。
通常(即,在没有volatile变量和同步的情况下),VM可以按照它想要的任何顺序将变量从一个线程可见到其他线程,或者根本不存在。例如。阅读线程可以读取另一个线程变量赋值的早期版本的混合。这是因为线程可能在具有自己的缓存的不同CPU上运行,这些缓存有时仅被复制到“主存储器”,并且还会通过代码重新排序以进行优化。
如果你使用了一个volatile变量,一旦线程B从中读取了一些值X,VM就会确保线程A在写入X之前写入的任何内容对B也是可见的。(以及A得到的所有内容)保证可见,过渡性。)
对同步块和其他类型的锁提供了类似的保证。