为什么’发生-之前’的关系被称为这样?

huangapple go评论89阅读模式
英文:

Why is 'happens-before; relationship called like that?

问题

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

英文:

I understand everything in this concept, except why is it called like that. Can someone please help me understand it? It still makes me confused.. That is my only question.
I read several articles, I still can't figure out motivation of making its name.

答案1

得分: 1

阅读这篇文章可能会有所帮助。

大致情况如下:JMM是根据“这个有限的事件集被定义为意味着某件事总是发生在另一件事之前”,这就是“happens before”的来源。然而,归根结底,这就是逻辑的转变:“如果JMM表示A发生在B之前,实际上意味着B之后的所有代码必须能够观察到A之前的一切。” 时间规则变为观察规则。

但是这个观察规则可能是您学到的,也是您理解JMM的方式,这是好的。然而,我假设“如果您添加同步块,这意味着其他线程将一致地观察您的更改,而如果不这样做,就不能保证它们会这样做”与英文单词“comes before”似乎没有关系。但是现在您知道了。

更深入一点

虚拟机希要能够重新排序操作,既在线程内部又在线程之间,因为这打开了优化的大门。然而,有时重新排序会破坏应用程序。那么虚拟机如何知道不要重新排序(虚拟机如何知道重新排序会破坏它)?通过如果虚拟机意识到两个事件之间存在时间关系,就不会重新排序这两个事件 - 当这两个事件依赖于一个事件应该在另一个事件之前发生的事实时。然后,JMM会确定哪些语言构造创建了这种时间关系,并要求我们Java开发人员编写我们的应用程序,以便如果我们依赖于某种顺序,就使用其中一个定义的happens before关系,以便虚拟机知道并且不会对我们重新排序。

它涉及三个方面:

  • 命令式:在单个线程内,所有语句在所有后续语句之前发生 - 这是显而易见的。在{x(); y();}中,虚拟机假设Java代码依赖于x()调用在y()调用之前发生,无论x和y是什么。

  • java.lang.Thread:在线程对象上调用.start()会在该线程实际启动之前发生。如果一个线程使用.join()等待另一个线程,那么在另一个线程中的所有操作都会在该join()返回之前发生。

  • 同步原语 - synchronized:如果您到达对象FOO上的同步块的末尾,代码依赖于在任何其他线程通过启动synchronized(FOO)来获取锁之前,这完全完成。

  • 同步原语 - volatile:字段写入先于后续的volatile字段读取。

因此,让我们回到它实际意味着什么,通过最后一个例子:这似乎是自证明的,不是吗?那说的是:“一件事发生在另一件事之前,意味着另一件事发生得更晚”。这就像“圆是圆的”。但它涉及到这些东西的意图以及它实际上是什么意思:

这与实际执行时间无关。它是关于能够见证其效果。

关于volatile的读写是在说:

如果线程A碰巧写入一个volatile,并且B碰巧看到了那个写入,那么这意味着A做的任何其他事情,无论是volatile还是synchronized,都必须对B可见。

因此,我们已经从“时间关系”转移到“可见性关系”,后者确实是JMM的意义所在,也是您可能理解的方式。希望现在您理解了我们是如何从“时间”转移到“可见性”的(在“发生在”在“它关乎时间”的背景下是显而易见的)。

英文:

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.

答案2

得分: 0

> A happens-before B does not imply A happening before B.

你说得对。我同意,这很令人困惑。

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

之所以称其为“happens before”,是为了强调这是一个**传递关系:如果你知道A“happens before”B,并且你还知道B“happens before”C,那么你可以推断出A“happens before”C。也就是说,程序必须表现得好像** A实际上发生在C之前。

这里有一个例子:有一个规则说,

对监视器的解锁在随后的对该监视器的每个锁之前发生。

听起来很明显!那这个呢?

如果x和y是同一个线程的动作,并且x在程序顺序中出现在y之前,则[x happens before y]。

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

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

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

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

把所有这些放在一起,你可以得出这样的结论:程序必须表现得好像线程1在线程2查看它们之前写入了a、b和c。

> 此外,A发生在B之前并不意味着A happens-before B。

正确。例如,让我们去掉锁。

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

“Happens before”规则是一个形式化的系统,定义了Java程序必须的行为。如果你无法找到一个“happens before”链条来证明你的程序会以你想要的方式行为,那么你的程序就有错误:JLS并不要求它以你认为的方式行为。

英文:

> A happens-before B does not imply A happening before B.

You're right. And I agree, it's confusing.

Whenever you see "A happens before B" in the Java Language Specification (JLS), you should understand it to mean, "The program must behave as if A happened before B."

The reason they call it "happens before" is to emphasize that it's a transitive relationship: If you know that A "happens before" B, and you also know that B "happens before" C, then you can deduce that A "happens before" C. That is to say, the program must behave as if A actually happens before C.

Here's an example: One rule says,

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

Seems pretty obvious! How about this one?

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

Do we even need to say that? Yes! because if you put them together, and if you remember to silently insert "the program must behave as if", then you can combine those two rules to reach a useful conclusion:

Suppose that thread 1 stores values into variables a, b, and c; and then it subsequently un-locks a lock. Also suppose that some time later, thread 2 locks the same lock and then it examines a, b, and c.

The first rule says that the program must behave as if the code that you wrote for thread 1 actually did all of the things that you told it to do in the order that you told it to do them (e.g., must assign a, b, and c, before unlocking the lock).

The second rule says that *IF* thread 2 actually does lock the lock after thread 1 released it, then the program must behave as if things really happened in that order. And, going back to the first rule, The program must behave as if thread 2 acquired the lock before it examined a, b, and c.

Put it all together, and you can conclude that the program must behave as if thread 1 wrote a, b, and c, before thread 2 looked at them.

> Also, A happening before B does not imply A happens-before B

Correct. For example, Let's take away the lock.

If thread 1 writes some variables, and then some actual time passes before thread 2 examines the same variables, but there's no locking and nothing else to establish a chain of "happens before" relationships; then the program is NOT required to behave as if the writes happened before the reads. And NOTE! on a modern multi-processor system, it's actually quite likely that the second thread could see an inconsistent view of a, b, and c (e.g., as if the first thread updated a, but not b or c.)

The "Happens before" rules are a formal system that defines how a Java program must behave. If you can't find a chain of "happens before" to prove that your program will behave in some way that you want it to do, then your program is in error: The JLS does not require that it behave in the way that you thought it would behave.

huangapple
  • 本文由 发表于 2020年8月19日 00:03:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/63472450.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定