易失变量和happens before排序

13

我有两个线程:

线程1:

a = 1;
x = b;

线程:2

b = 1
y = a

这里声明了a和b为volatile。我不理解在a = 1;和y = a;以及x = b;和b = 1;之间如何创建"happens-before"边缘。

我知道使用volatile变量可以防止从线程缓存读取旧值。但是,volatile变量如何确保happens-before顺序。

具体来说,我不理解这句话:

  

对volatile字段的写入发生在每个随后对相同字段的读取之前。

它是如何工作的?


请参考最近的这个问题和答案,获取大量相关信息:https://dev59.com/c2865IYBdhLWcg3wIrFg - andersoj
3个回答

16
对于同一字段的每个后续读取,对易失性字段的写入都会发生在其之前。这里重要的词是“后续”。以下是Java语言规范17.4.4同步顺序的相关部分:
每次执行都有一个同步顺序。同步顺序是执行中所有同步操作的全序。对于每个线程t,在t中的同步操作(§17.4.2)的同步顺序与t的程序顺序(§17.4.3)一致。同步操作引起了在动作上的同步关系,定义如下:
[...]
对易失变量(§8.3.1.4)v的写入与任何线程对v的后续读取同步(其中“后续”根据同步顺序定义)。
请注意最后一部分。因此,它表示,如果您考虑程序的所有操作的任何总排序,任何后来的读取易失性变量的读取都不会“错过”写入。

对于每个线程t,同步操作的同步顺序是什么?仅靠volatile本身无法确定线程之间的同步顺序,不是吗? - user166390
我也有同样的问题。我觉得 Java 规范有点晦涩难懂。 - softwarematter
1
@pst:volatile关键字适用于“总体”顺序,而不是特定线程的顺序。这实际上意味着一个线程在读取一个volatile变量之前必须检查是否有其他线程执行了写操作。 - Jon Skeet
@JonSkeet,我读了JLS,我认为“同步顺序”和“先于发生顺序”不是一回事,“先于发生顺序”适用于两个冲突访问同时发生的情况。 - khotyn
@JonSkeet 正如您所说:“线程在读取易失性变量之前必须检查是否有其他线程执行了写操作。”但是如果一个线程正在进行写操作,而第二个线程正在读取它,会发生什么情况!!我错了吗??这不是原子操作的竞争条件吗??请回复。 - hardik

5

为了进行分析,首先列出所有可能的同步顺序,这些顺序必须与编程顺序一致。在你的例子中,有6个可能的顺序。

 1       2       3       4       5       6
w(a)    w(a)    w(b)    w(a)    w(b)    w(b) 
r(b)    w(b)    w(a)    w(b)    w(a)    r(a)
w(b)    r(b)    r(b)    r(a)    r(a)    w(a)
r(a)    r(a)    r(a)    r(b)    r(b)    r(b)

每个订单都会建立一些先后顺序关系。在(1)中,我们有w(a)发生在r(a)之前。在(6)中,我们有w(b)发生在r(b)之前。在(2)-(5)中,我们两者都有。
对于每种可能的顺序,根据它所建立的先后顺序关系,您需要分析执行以确保它做您想要的事情。
如果听起来太难了,那就是这样。在现实生活中,我们通常限制自己处理更简单的情况,例如只锁定/释放一个对象或只读写一个易失性变量。然后它就不会太复杂。

3
对于volatile字段的写操作,发生在相同字段的随后的每个读操作之前。
这段话很令人困惑。它将happens-before关系限制为发生在读取之前的写入!它只表示在之后的读取实际上是在之后发生的。
换句话说,它尝试表达的意思是,读取不会在写入期间发生,并且如果有其他happens-before关系导致读取在写入之后发生,则读取将具有该写入的值。
请参见JLS章节17.4.4同步顺序,其中定义了此上下文中的“后续”一词。

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