易失性变量和其他变量

15
以下内容摘自经典著作《Java并发编程实践》:
当线程A写入一个volatile变量后,随后线程B读取同一变量时,所有在A写入volatile变量之前可见的变量的值都将对B可见。
这个语句可能有些微妙之处,比如在这个上下文中,“所有变量”指什么?这是否意味着使用volatile关键字也会影响非volatile变量的使用?
作者不确定自己是否完全理解了这个陈述。您需要帮助吗?
2个回答

14

你问题的答案在JLS #17.4.5中:

对 volatile 字段(§8.3.1.4)的写操作 happens-before 该字段之后的每次读操作。

因此,如果一个线程中有以下代码:

aNonVolatileVariable = 2 //w1
aVolatileVariable = 5 //w2

随后在另一个线程中:

someVariable = aVolatileVariable //r1
anotherOne = aNonVolatileVariable //r2
即使该变量不是volatile,您也有保证anotherOne将等于2。因此,使用volatile也会影响非volatile变量的使用。
更详细地说,这是由Java Memory Model (JMM)在同一部分提供的两个其他保证:intra thread order和transitivity所造成的(hb(x, y)表示x发生在y之前):
如果x和y是同一线程的动作,并且x在程序顺序中先于y,则hb(x, y)。 [...] 如果hb(x, y)hb(y, z),则hb(x, z)
在我的例子中: - hb(w1, w2)hb(r1, r2) (intra thread semantics) - 因为volatile的保证,hb(w2, r1) 因此可以得出通过传递性推断出hb(w1, r2)
JMM还保证,如果与happens-before关系正确同步,则程序的所有执行都将是sequential consistent(即看起来没有重新排序)。因此,在这种特定情况下,保证非volatile读取将看到非volatile写入的效果。

1
很抱歉,我无法看出这个保证是如何从“JSL”的声明中得出的。它只是说对易失性变量的写操作在读取该字段之前发生。但这又如何保证在另一个线程中,“anotherOne”可见为“2”呢? - Cratylus
1
我理解hb(w1, r2)。目前为止还好。但是为什么在这里规定2的更新值可见呢?写入可以发生在读取之前,但是读取使用的是缓存值而不是从w1获取的值,这种情况难道不可能发生吗?这是我无法理解的部分。 hb(w1, r2)的定义是否还包括可见性方面的内容? - Cratylus
@Cratylus JMM保证正确同步的程序将是顺序一致的“这对程序员来说是一个非常强有力的保证。[...] 一旦确定代码已经正确同步,程序员就不需要担心重新排序会影响他或她的代码。” - assylias
@Cratylus 同一部分中 JLS 的另一句话:“在一个 happens-before 一致的操作集中,每个读取都会看到它可以通过 happens-before 排序看到的写入。” - assylias
这些评论是否涉及由于重新排序导致的非可见性问题?好的,重新排序不会发生,但是寄存器中缓存值呢? - Cratylus
@Cratylus "Sequentially consistent" 保证了可见性。这里是另一条引用(17.4.3):“顺序一致性是关于程序执行中可见性和排序的非常强的保证。在顺序一致的执行中,所有单个操作(如读写)都有一个总顺序,该顺序与程序的顺序一致,并且每个单个操作都是原子的并且立即对每个线程可见。” - assylias

9

这意味着如果您写入十个非易失性变量并写入易失性变量,则必须在易失性变量之前设置所有非易失性变量。

如果您读取易失性变量和所有非易失性变量,则可以确保顺序不会交换。


2
易失性变量中设置的值的顺序相对于其他变量(易失性或非易失性)不能改变,但是对于非易失性变量可能会发生这种情况。这意味着当您设置易失性变量时,即使它们不是易失性的,所有先前设置的值也将被缓存一致化。 - Peter Lawrey
它们将被缓存,但CPU确保每个缓存看到相同的值(或在需要时会要求)每个芯片插槽都有2或3级多个缓存。它们有一条通信总线,在需要时确保彼此同步。即使是多个芯片插槽也会进行通信以保持同步,避免从主内存读/写值。 - Peter Lawrey
我认为这里有一些混淆。我看到很多人评论说volatile关键字会影响处理器缓存的一级和二级缓存等。我不明白这可能是真的。首先,缓存一致性协议保证了多个线程对同一内存位置的视图始终同步。更有可能的是,虚拟机为每个线程维护一个单独的内存缓存区域,并且volatile关键字调用一些代码来使它们同步。 - Darren
JVM只使用执行高速缓存一致性读写或简单读写的指令,而不强制执行缓存一致性的指令。它不会执行CPU不支持的任何操作。此外,JVM可以针对非易失性字段进行读取优化,这意味着它们可能永远不会看到更新,因为它们在同一线程中未被更改。 - Peter Lawrey
彼得,我的观点是,我认为你所提到的缓存并不是处理器缓存。它应该是由虚拟机维护的缓存。换句话说,如果两个线程读取了两个不同的值,则它们正在从两个不同的内存地址中读取。如果两个线程从同一地址读取,则处理器的缓存一致性协议将保证它们都接收到相同的值。这就是我的理解。如果我错了,我想知道。 - Darren
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接