理解 happens-before 和同步

Understanding happens-before and synchronization

我正在尝试理解 Java happens-before order 概念,但有几件事看起来很混乱。据我所知,happens before 只是一组动作的顺序,并不提供任何关于实时执行顺序的保证。实际上(强调我的):

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

所以,它只是说,如果有两个动作 w(写入)和 r(读取)使得 hb(w, r),比 r 可能 实际上在执行中发生在 w 之前,但不能保证它会发生。写入 w 也被读取 r.

观察到

如何确定两个动作在 运行 时间内相继执行?例如:

public volatile int v;
public int c;

操作:

Thread A
v = 3;  //w

Thread B
c = v;  //r

这里我们有 hb(w, r) 但这并不意味着 c 将在赋值后包含值 3。我如何强制将 c 分配给 3? synchronization order是否提供此类保证?

我想将上述语句与一些示例代码流相关联。

为了理解这一点,让我们来看下面的 class,它有两个字段 counterisActive

class StateHolder {
    private int counter = 100;
    private boolean isActive = false;

    public synchronized void resetCounter() {
            counter = 0;
            isActive = true;
    }

    public synchronized void printStateWithLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }

    public void printStateWithNoLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }
}

并假设有三个线程T1、T2、T3在StateHolder的同一个对象上调用以下方法:

T1同时调用resetCounter(),T2同时调用printStateWithLock(),T1获得锁
T3 -> 在 T1 完成执行后调用 printStateWithNoLock()

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

并且第一行说,

根据上面的语句,它为 JVM、OS 或底层硬件提供了灵活性,可以对 resetCounter() 方法中的语句进行重新排序。当 T1 被执行时,它可以按以下顺序执行语句。

    public synchronized void resetCounter() {
            isActive = true;
            counter = 0;
    }

这与声明内联不一定意味着它们必须在实施中按该顺序发生。

现在从 T2 的角度来看,这种重新排序没有任何负面影响,因为 T1 和 T2 都在同一个对象上同步,并且 T2 保证看到两个字段的更改,无论重新排序是否发生,因为存在先行关系。所以输出总是:

Counter : 0
IsActive : true

这是根据声明,如果重新排序产生的结果与合法执行一致,则不违法

但是从 T3 的角度来看,通过这种重新排序,T3 可能会将 isActive 的更新值视为“truebut still see thecountervalue as100”,尽管T1 已完成执行。

Counter : 100
IsActive : true

上面的下一点link进一步澄清了声明并说:

More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

在此示例中,T3 遇到了此问题,因为它与 T1 或 T2 没有任何发生前关系。这符合 对于与它们不共享先发生关系的任何代码,不一定必须以该顺序出现。

注意: 为了简化案例,我们让单线程 T1 修改状态,T2 和 T3 读取状态。有可能

T1 更新 counter to 0,稍后
T2 将 isActive 修改为 true 并在一段时间后看到 counter is 0
打印状态的 T3 仍然可以看到 only isActive as true but counter is 100,尽管 T1 和 T2 都已完成执行。

关于最后一个问题:

we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3?

public volatile int v;
public int c;

Thread A
v = 3;  //w

Thread B
c = v;  //r

因为 v 是易变的,根据 Happens-before Order

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

因此可以安全地假设,当线程 B 尝试读取变量 v 时,它将始终读取更新后的值,并且 c 将在上面的代码中分配 3。

当 JLS 说线程 A 中的某个事件 X 与线程 B 中的事件 Y 建立了 先于 关系时,并不意味着 X 将先于 Y 发生。

表示IFX发生在Y之前,那么两个线程都会同意X发生在Y之前。也就是说,两个线程都将看到程序的内存处于与 X 发生在 Y 之前一致的状态。


全靠记忆。线程通过共享内存进行通信,但是当一个系统中有多个CPU时,都试图访问同一个内存系统,那么内存系统就会成为瓶颈。因此,典型的多 CPU 计算机中的 CPU 可以延迟、重新排序和缓存内存操作以加快速度。

这在线程不相互交互时效果很好,但当它们确实想要交互时会导致问题:如果线程 A 将值存储到普通变量中,Java 无法保证何时(甚至 if)线程 B 将看到值更改。

为了在重要的时候解决这个问题,Java 为您提供了同步 线程的某些方法。也就是说,让线程就程序内存的状态达成一致。 volatile关键字和synchronized关键字是线程间建立同步的两种方式。


我认为之所以叫它"happens before"是为了强调关系的传递性:如果你能证明A发生在B之前,并且你能证明B发生在C之前,那么根据JLS中指定的规则,你已经证明了A发生在C之前。

根据我的喜好解释@James 的回答:

// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;

// Thread A
first = 5;
second = 6;
third = 7;
hasValue = true;

// Thread B
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first);  // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third);  // will print 7

if you want the state/value of the memory(memory and CPU cache) seen at the time of a write statement of a variable by one thread,

线程 A 中 hasValue=true(写入语句)看到的内存状态:

first 的值为 5,second 的值为 6,third 的值为 7

to be seen from every subsequent(why subsequent even though only one read in Thread B in this example? We many have Thread C doing exactly similar to Thread B) read statement of the same variable by another thread,then mark that variable volatile.

如果线程 A 中的 X (hasValue=true) 发生在线程 B 中的 Y (sysout(hasValue)) 之前,则行为应该如同 X 发生在之前同一线程中的 Y(在 X 处看到的内存值应该从 Y 开始相同)

Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?

还有你的例子

public volatile int v;
public int c;
Actions:

Thread A
v = 3;  //w

Thread B
c = v;  //r

您的示例中 v 不需要 volatile。我们来看一个类似的例子

int v = 0;
int c = 0;
volatile boolean assigned = false;

操作:

线程 A

v = 3;
assigned = true;

线程 B

while(!assigned);
c = v;
  1. assigned 字段易变。
  2. 只有在 assigned 变为 true 之后,我们才会在 Thread B 中有 c = v 语句(while(!assigned) 负责)。
  3. 如果我们有 volatile — 我们有 happens before.
  4. happens before 意味着,如果我们看到 assigned == true — 我们将看到语句 assigned = true 之前发生的所有事情:我们将看到 v = 3.
  5. 所以当我们有 assigned == true -> 我们有 v = 3.
  6. 结果是c = 3

没有volatile

会怎样
int v = 0;
int c = 0;
boolean assigned = false;

操作:

线程 A

v = 3;
assigned = true;

线程 B

while(!assigned);
c = v;

目前有 assigned 没有 volatile

Thread Bc的值在这种情况下可以等于0或3。所以没有任何保证 c == 3.