“为什么是”以前发生过;你这么叫我?

2skhul33  于 2021-07-06  发布在  Java
关注(0)|答案(2)|浏览(302)

我理解这个概念中的所有东西,除了为什么叫它那样。有人能帮我理解吗?它仍然让我困惑。。这是我唯一的问题。我读了好几篇文章,还是搞不懂它成名的动机。

bhmjp9jg

bhmjp9jg1#

读这篇文章可能会有所帮助。
一般的要点是:jmm是根据“这一有限的事件集被定义为意味着一件事总是发生在另一件事之前”来定义的,这就是术语“发生在之前”的来源。然而,归根结底,这是逻辑上的跳跃:“如果jmm说a发生在b之前,那实际上意味着b之后的所有代码都必须能够观察到a之前的一切。”。时间规则变成观察规则。
但是这个观察法则是你可能学到的,也是你如何理解jmm的,这很好。但是,我假设“如果您添加了一个同步块,这意味着其他线程将一致地观察您的更改,而如果您不这样做,则不能保证它们“似乎与英语单词‘becomes before’无关”。但现在你知道了。

更深入一点

vm希望能够重新排序线程内和线程间的操作,因为这为优化打开了大门。然而,有时重新排序会破坏应用程序。那么vm如何知道不重新排序(vm如何知道重新排序会破坏它)?如果vm意识到这两个事件之间存在时序关系,则不重新排序这两个事件,因为这两个事件依赖于一个事件应该先于另一个事件发生的事实。jmm然后指出哪些语言构造创建了这样的时序关系,并要求我们的java程序员编写我们的应用程序,这样如果我们依赖某个顺序,我们就可以在关系发生之前使用其中一个定义的关系,这样vm就知道并且不会对我们重新排序。
有三件事:
命令式:在一个线程中,所有语句都发生在所有其他语句之前-这是显而易见的一个。在: {x(); y();} ,vm假设java代码依赖于在y()调用之前发生的x()调用,不管x和y是什么。
java.lang.thread:在线程对象实际启动之前调用.start()。如果一个thread.join()是另一个线程,则另一个线程中的所有操作都会在该join()返回之前发生。
sync primitives-synchronized:如果在对象foo上到达synchronized()块的末尾,代码依赖于这样一个事实,即在任何其他线程通过启动synchronized(foo)来获取锁之前,这个过程已经完全完成。
同步原语-volatile:字段写入发生在以后的volatile字段读取之前。
所以让我们回到它的真正含义,通过最后一个:它似乎是同义反复,不是吗?也就是说:“一件事发生在另一件事之前,意味着另一件事发生在之后”。就像“圆是圆”。但这与这些东西的意图以及它的真正含义有关:
这与实际执行时间无关。是为了能亲眼目睹它的影响。
volatile reads/writes的意思是:
如果线程a碰巧写入了一个volatile,而线程b碰巧看到了该写入,那么这意味着线程a所做的任何事情,volatile/同步或不同步,都必须对线程b可见。
因此,我们已经从“时间关系”转移到了“可见性关系”,后者就是jmm的真正意义所在,也就是您理解它的方式。希望现在你能理解我们是如何从“时间”中获得“可见性”的(而“之前发生的事情”很明显,因为“它与时间有关”,大概是这样)。
举个例子:

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);
    }
}

在这里,vm可以接受以打印结束:

0 0
0 40

但这似乎毫无意义(在这里,线程b在线程a之前运行),但是不知怎么的,第二个字段写入是可见的,但是第一个字段不可见?嗯?-但这就是它的工作原理,jmm不能保证。
不过,如果再加上易变的文字,你就无法再观察到这一点了。

e37o9pze

e37o9pze2#

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)。
第二条规则是,如果线程2在线程1释放锁之后确实锁定了它,那么程序的行为必须像事情真的是按照这个顺序发生的一样。并且,回到第一条规则,程序的行为必须像线程2在检查a、b和c之前获取了锁一样。
把它们放在一起,您可以得出这样的结论:在线程2查看它们之前,程序的行为必须像线程1编写了a、b和c一样。
而且,a发生在b之前并不意味着a发生在b之前
对的。例如,让我们把锁拿走。
如果线程1写了一些变量,然后在线程2检查相同的变量之前经过了一些实际的时间,但是没有锁定,也没有其他东西来建立一系列“发生在”之前的关系;这样,程序就不必表现为写操作发生在读操作之前。注意!在现代的多处理器系统中,第二个线程很可能看到a、b和c的不一致视图(例如,好像第一个线程更新了a,但没有更新b或c)
“before”规则是一个正式的系统,它定义了java程序必须如何运行。如果您找不到一系列“之前发生”来证明您的程序将以您希望的方式运行,那么您的程序就是错误的:jls不要求它以您认为的方式运行。

相关问题