-1

我理解这个概念的所有内容,除了为什么这样称呼它。有人可以帮我理解吗?它仍然让我感到困惑..这是我唯一的问题。我读了几篇文章,我仍然无法弄清楚它的名字的动机。

4

2 回答 2

1

Reading this article will probably help.

The general gist is: The JMM is defined in terms of 'this limited set of events are defined to imply that one thing always happens before the other thing', which is where the term 'happens before' comes from. However, what that boils down to, is this jump in logic: "if the JMM says that A happens before B, what that actually means is that all code after B must be able to observe everything up to A.". Timing rules turn into observation rules.

But that observation rule is what you probably learned and how you understand the JMM, and that's good. However, I assume that 'if you add a synchronized block, it means other threads will observe your changes consistently, whereas if you do not, there is no guarantee that they would' doesn't seem related to the english words 'comes before'. But now you know.

A bit more in-depth

The VM wants to be able to re-order actions, both intra-thread and inter-thread, because that opens the door to optimizations. However, sometimes reordering would break the app. So how does the VM know not to re-order (how does the VM know that re-ordering would break it)? By NOT reordering 2 events if the VM realizes that there is a timing relationship between the 2 events - when the 2 events rely upon the fact that one should happen before the other. The JMM then breaks out which language constructs create such timing relationships, and demands that us java coders write our apps so that if we rely on a certain order, that we use one of these defined happens before relationships so that the VM knows and won't reorder on us.

It's 3 things:

  • Imperative: Within a single thread, all statements happen before all further statements - this is the obvious one. In: {x(); y();}, the VM assumes that the java code relies on the x() invoke happening before the y() invoke, whatever x and y are.

  • java.lang.Thread: calling .start() on a thread object happens-before that thread actually starting. If a thread .join()s another thread, all actions in the other thread happen-before that join() returns.

  • sync primitives - synchronized: If you hit the end of a synchronized() block on object FOO, code relies on the fact that this is fully completed before any other thread would then acquire the lock by starting a synchronized(FOO).

  • sync primitives - volatile: field writes happen before later volatile field reads.

So let's go back to what it really means, by way of that last one: It seems tautologous, no? That says: "A thing that happens before another thing, means the other thing happened later". That's like "Circles are round". But it goes to the intent of this stuff and what it really means:

It's not so much about actual execution times. It's about being able to witness the effects of it.

The volatile reads/writes thing is saying:

If thread A so happens to write to a volatile and B so happens to see that write, then that means anything else A did, volatile/synchronized or not, must also be visible to B then.

And thus we have moved from 'timing relationships' to 'visibility relationships', and that latter one is really how the JMM makes sense, and presumably how you understand it. Hopefully now you understand how we got to 'visibility' from 'timing' (and 'happens before' is obvious in light of 'it is about timing', presumably).

Here is an example:

class Example {
    public int field1 = 0;
    public int field2 = 0;

    public void runInA() {
        int f1 = field1, f2 = field2;
        field1 = 5;
        field2 = 10;
        System.out.println(f1 + " " + f2);
    }

    public void runInB() {
        int f1 = field1, f2 = field2;
        field1 = 20;
        field2 = 40;
        System.out.println(f1 + " " + f2);
    }
}

Here it would be acceptable for a VM to end up printing:

0 0
0 40

But that seems to make no sense! (Here thread B ran before A) - but somehow the second field write is visible, but the first one isn't? Huh? - but that's how it works, the JMM makes no guarantees.

Toss in a volatile write, though, and you can no longer observe this.

于 2020-08-18T16:25:58.777 回答
0

A发生在B之前并不意味着A发生在B之前。

你是对的。我同意,这令人困惑。

每当您在 Java 语言规范 (JLS) 中看到“A 发生在 B 之前”时,您应该将其理解为“程序必须表现得好像A 发生在 B 之前一样”。

之所以称它为“发生在”之前是为了强调它是一种传递关系:如果您知道A“发生在”B之前,并且您也知道B“发生在”C之前,那么您可以推断A“发生在之前” ” C。也就是说,程序必须表现得好像A 实际发生在 C 之前一样。

这是一个例子:一条规则说,

An unlock on a monitor happens-before every subsequent lock on that monitor.

看起来很明显!这个怎么样?

If x and y are actions of the same thread and x comes before y in program
order, then [x happens before y].

我们还需要这么说吗?是的!因为如果你把它们放在一起,如果你记得默默地插入“程序必须表现得好像”,那么你可以结合这两个规则来得出一个有用的结论:

假设线程 1 将值存储到变量 a、b 和 c 中;然后它随后解锁了一个锁。还假设一段时间后,线程 2 锁定了同一个锁,然后它检查 a、b 和 c。

第一条规则说程序必须表现得好像您为线程 1 编写的代码实际上按照您告诉它的顺序执行了您告诉它做的所有事情(例如,必须分配 a、b、 c,解锁之前)。

第二条规则说*IF*线程 2 实际上确实在线程 1 释放锁之后锁定了锁,那么程序必须表现得好像事情真的按照那个顺序发生了一样。而且,回到第一条规则,程序必须表现得好像线程 2 在检查 a、b 和 c之前获得了锁。

把它们放在一起,你可以得出结论,线程 2 看到它们之前,程序必须表现得好像线程 1 写了 a、b 和 c 。

此外,A 发生在 B 之前并不意味着 A 发生在 B 之前

正确的。例如,让我们拿走锁。

如果线程 1 写入了一些变量,然后在线程 2 检查相同的变量之前经过了一些实际时间,但没有锁定,也没有其他任何东西可以建立“发生在”关系链;那么程序不需要表现得好像写入发生在读取之前。注意!在现代多处理器系统上,实际上很可能第二个线程可能会看到 a、b 和 c 的不一致视图(例如,好像第一个线程更新了 a,但没有更新 b 或 c。)

“发生在之前”规则是一个正式的系统,它定义了 Java 程序的行为方式。如果您找不到“之前发生”的链来证明您的程序将以您希望的方式运行,那么您的程序是错误的:JLS 不要求它以您希望的方式运行认为它会表现。

于 2020-08-18T18:19:17.580 回答