以下是经典Concurency in Practice的内容:
When thread A writes to a volatile variable and subsequently thread B
reads the same variable, the values of all variables that were
visible to A prior to writing to the volatile variable, become visible
to B after reading the volatile variable.
我不确定我是否真的能理解这一说法。 例如,在这种情况下,所有变量的含义是什么? 这是否意味着使用volatile也会对非易失性变量的使用产生副作用?
在我看来,这种说法具有我无法理解的一些微妙含义。
有什么帮助吗?
您的问题的答案在JLS#17.4.5中:
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
所以如果在一个线程中
aNonVolatileVariable = 2 //w1
aVolatileVariable = 5 //w2
然后在另一个线程中:
someVariable = aVolatileVariable //r1
anotherOne = aNonVolatileVariable //r2
您可以保证anotherOne等于2,即使该变量不是volatile。因此,是的,使用volatile对使用非易失性变量也有副作用。
更详细地说,这是由于同一部分中的Java内存模型(JMM)提供了2个其他保证:线程内顺序和可传递性(hb(x,y)表示x发生在y之前):
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
[...]
If hb(x, y) and hb(y, z), then hb(x, z).
在我的示例中:
hb(w1,w2)和hb(r1,r2)(线程内语义)
hb(w2,r1)由于易失性保证
因此您可以通过传递性得出hb(w1,r2)的结论。
而且,JMM保证,如果程序与事前发生的关系正确同步,则该程序的所有执行将保持顺序一致(即,看起来好像什么都没有重新排序)。因此,在这种特定情况下,非易失性读取可确保看到非易失性写入的效果。
很抱歉,我无法从JSL的语句中看到这种保证,它只表示对volatile happens-before的写操作读取了该字段,这又如何保证在另一个线程中< x6>可见为2?
到目前为止,我理解hb(w1, r2) .Ok,但是为??什么在这里强制要求2的更新值可见?难道是,写入发生在之前,但是读取使用了一个缓存值而不是该值从w1开始?这是我无法获得的部分。hb(w1, r2)的定义是否还包括可见性方面?
@Cratylus JMM保证正确同步的程序(具有先发生的关系)将保持顺序一致:"对于程序员,这是极其有力的保证。[...]一旦确定代码正确同步,程序员不必担心重新排序会影响他或她的代码。"
@Cratylus在同一部分中,JLS的另一句话是:"在一组一致的动作之前发生的情况下,每次读取都会看到一个写,该顺序允许在顺序发生之前看到它。"
这些说明不能解决由于重新排序而导致的不可见性吗?不会进行重新排序,但是如何在寄存器中缓存值呢?
@Cratylus"顺序一致"保证可见性。这是另一个引言(17.4.3):"顺序一致性是对程序执行中的可见性和顺序的非常有力的保证。在顺序一致的执行中,所有单个动作(例如读写),这与程序的顺序是一致的,并且每个动作都是原子的,每个线程都可以立即看到。"
这意味着如果您要写入10个非易失性变量并写入易失性变量,则必须在易失性变量之前设置所有非易失性变量。
如果您读取了volatile变量和所有非易失性变量,则可以确保订单不会被交换。
你在说什么顺序?volatile变量不能重新排序,好,所以volatile变量不应该用非易失性重新演算并最后写入,但是非易失性变量的顺序可以还可以改变吧?
在易失性变量中设置的值的顺序不能相对于其他变量(易失性或其他)更改,但是对于非易失性变量可能会发生这种情况。这意味着,当您设置易失性变量时,所有先前设置的值即使不是易失性的,也会被设置为缓存连贯的。
什么是缓存一致性?它们也不会被缓存?
它们将被缓存,但是CPU确保每个缓存看到相同的值(或将按需请求)。每个套接字具有2个或3个级别的多个缓存。它们具有通信总线,以确保它们在需要时彼此同步。甚至多个套接字也会通信以保持同步,从而避免了必须从主存储器中写入/读取值的情况。
但是为什么非易失性变量需要并保证这种同步呢,仅仅因为它们恰好是在写入volatile之前发生的,它们如何受到影响?
同步以一致的方式提供了更多的保证,例如读取和写入,例如增量。仅阅读或写作通常不是您所需要的。 volatile的定义使得在它之前发生的所有写入都不得在它之后发生。
all writes which occur before it must not occur after it。我理解这一点,但这如何取决于/对缓存一致性产生副作用?这是我不关注的部分
我认为这里有些混乱。我见过很多人评论说,volatile关键字会影响第一级和第二级缓存等的处理器缓存一致性。我不知道这怎么可能是正确的。一方面,高速缓存一致性协议可确保同一内存位置的多个线程视图始终保持同步。 VM更有可能为每个线程维护一个单独的内存缓存区域,而volatile关键字调用一些用于同步它们的代码。
JVM仅使用执行高速缓存一致性读取和写入的指令或不执行高速缓存一致性的简单读取和写入的指令。它不执行CPU本身不支持的任何操作。此外,JVM可以优化非易失性字段的读取方式,这意味着它们永远不会看到更新,因为它们不会在同一线程中被更改。
彼得,我的意思是,我不认为您所指的缓存与处理器缓存相同。而是由VM维护的缓存。换句话说,如果两个线程读取两个不同的值,则它们正在从两个不同的内存地址读取。如果两个线程从同一地址读取,则处理器缓存一致性协议将保证它们都接收相同的值。那是我的理解。如果我错了,那么我想知道。
VM不维护此类缓存。当您从两个不同的缓存中读取相同的地址时,可以获得不同的值。并非对所有CPU指令都强制执行缓存一致性。