发生在推理之前应用于一些例子
我们将提供一些示例来说明如何应用发生之前的推理来检查写入对后续读取是否可见。
单线程代码
正如你所期望的那样,写入对于单线程程序中的后续读取始终可见。
public class SingleThreadExample {
public int a, b;
public int add() {
a = 1; // write(a)
b = 2; // write(b)
return a + b; // read(a) followed by read(b)
}
}
通过发生 - 在规则#1 之前:
write(a)
动作发生在write(b)
动作之前。write(b)
动作发生在read(a)
动作之前。read(a)
动作发生在read(a)
动作之前。
通过发生 - 在规则#4 之前:
write(a)
发生 - 在write(b)
和write(b)
发生之前 - 在read(a)
IMPLIESwrite(a)
发生之前 - 发生之前 12。write(b)
发生 - 在read(a)
和read(a)
发生之前 - 在read(b)
之前发生了write(b)
发生 - 在read(b)
之前。
加起来:
write(a)
发生 - 在read(a)
关系之前意味着a + b
语句保证看到a
的正确值。write(b)
发生 - 在read(b)
关系之前意味着a + b
语句保证看到b
的正确值。
具有 2 个线程的示例中的 volatile
行为
我们将使用以下示例代码来探讨内存模型对于 volatile 的一些含义。
public class VolatileExample {
private volatile int a;
private int b; // NOT volatile
public void update(int first, int second) {
b = first; // write(b)
a = second; // write-volatile(a)
}
public int observe() {
return a + b; // read-volatile(a) followed by read(b)
}
}
首先,考虑涉及 2 个线程的以下语句序列:
- 创建了一个
VolatileExample
实例; 叫它ve
, ve.update(1, 2)
在一个线程中被调用,并且ve.observe()
在另一个线程中被调用。
通过发生 - 在规则#1 之前:
write(a)
动作发生在volatile-write(a)
动作之前。volatile-read(a)
动作发生在read(b)
动作之前。
通过发生 - 在规则#2 之前:
- 第一个线程中的
volatile-write(a)
动作发生在第二个线程中的volatile-read(a)
动作之前。
通过发生 - 在规则#4 之前:
- 第一个线程中的
write(b)
动作发生在第二个线程中的read(b)
动作之前。
换句话说,对于这个特定的序列,我们保证第二个线程将看到第一个线程对非易失性变量 b
的更新。但是,还应该清楚的是,如果 update
方法中的赋值是相反的,或者 observe()
方法在 a
之前读取变量 b
,那么之前发生的链将被破坏。如果第二个线程中的 volatile-read(a)
不在第一个线程中的 volatile-write(a)
之后,链也会被破坏。
当链断裂时,无法保证 observe()
能看到 b
的正确值。
挥发性有三个线程
假设我们在前面的例子中添加第三个线程:
- 创建了一个
VolatileExample
实例; 叫它ve
, - 两个线程称为
update
:ve.update(1, 2)
在一个线程中被调用,- 在第二个帖子中调用
ve.update(3, 4)
,
- 随后在第三个线程中调用
ve.observe()
。
要完全分析这一点,我们需要考虑第一个线程和第二个线程中语句的所有可能交错。相反,我们只考虑其中两个。
场景#1 - 假设 update(1, 2)
在 update(3,4)
之前我们得到这个序列:
write(b, 1), write-volatile(a, 2) // first thread
write(b, 3), write-volatile(a, 4) // second thread
read-volatile(a), read(b) // third thread
在这种情况下,很容易看出从 write(b, 3)
到 read(b)
之前有一个完整的发生链。此外,没有干预写入 b
。因此,对于这种情况,第三个线程保证看到 b
具有值 3
。
场景#2 - 假设 update(1, 2)
和 update(3,4)
重叠并且交错如下:
write(b, 3) // second thread
write(b, 1) // first thread
write-volatile(a, 2) // first thread
write-volatile(a, 4) // second thread
read-volatile(a), read(b) // third thread
现在,虽然从 write(b, 3)
到 read(b)
有一个发生在前的链,但是另一个线程执行了干预的 write(b, 1)
动作。这意味着我们无法确定 read(b)
会看到哪个值。
(旁白:这表明我们不能依赖 volatile
来确保非易失性变量的可见性,除非在非常有限的情况下。)