简体   繁体   English

同步块后读取的可见性

[英]Visibility of a read after synchronized block

Does JMM guarantee the visibility of a synchronized write to the variable that is read in the other thread after a synchronized block? JMM 是否保证在synchronized块之后对在另一个线程中读取的变量进行synchronized写入的可见性? Here's what I mean:这就是我的意思:

public class SynchronizedWriteRead {

    private int a;
    private int b;

    public void writer() {
        synchronized (this) {
            a = 5;
            b = 7;
        }
    }

    public void reader() {
        synchronized (this) {
            int r1 = a; /* 5 */
        }
        int r2 = b; /* ? */
    }
}

JMM guarantees that an unlock on a monitor happens-before every subsequent lock on that monitor. JMM 保证监视器上的解锁发生在该监视器上的每个后续锁定之前。 But I'm not sure if it relates only to the synchronized block body or not.但我不确定它是否仅与synchronized块体有关。

Recently I'm encountered this post from Aleksey Shipilëv - Safe Publication and Safe Initialization in Java .最近我在 Java 中遇到了 Aleksey Shipilëv 的这篇文章 - 安全发布和安全初始化 It says:它说:

Notice how doing synchronized in Unsafe DCL store does not help, contrary to layman belief it somehow magically "flushes the caches" or whatnot.请注意,在Unsafe DCL存储中进行synchronized是如何无济于事的,这与外行人的看法相反,它以某种方式神奇地“刷新了缓存”或诸如此类的东西。 Without a paired lock when reading the protected state, you are not guaranteed to see the writes preceding the lock-protected write.在读取受保护的 state 时,如果没有配对锁,则无法保证在锁保护写入之前看到写入。

So this is why I asked myself this question.所以这就是我问自己这个问题的原因。 I couldn't find an answer in the JLS .我在JLS中找不到答案。

Let's put it another way.让我们换一种说法。 Sometimes you're piggybacking on a volatile happens-before guarantee like this:有时你会捎带一个volatile发生之前的保证,如下所示:

public class VolatileHappensBefore {

    private int a; /* specifically non-volatile */
    private volatile int b;

    public void writer() {
        a = 5;
        b = 7;
    }

    public void reader() {
        int r1 = b; /* 7 */
        int r2 = a; /* 5 */
    }
}

You're guaranteed to see both writes because sequential actions in the same thread are backed by happens-before, and happens-before itself is transitive.因为同一线程中的顺序操作由happens-before 支持,并且happens-before 本身是可传递的,所以您可以保证看到这两种写入。

Can I use a synchronized happens-before guarantee the same way?我可以以相同的方式使用synchronized发生之前的保证吗? Maybe even like this (I've put sync variable to forbid the compiler/JVM to remove otherwise empty synchronized block):甚至可能像这样(我已经放置了sync变量以禁止编译器/JVM删除其他空的synchronized块):

    public void writer() {
        a = 5;
        b = 7;
        synchronized (this) {
            sync = 1;
        }
    }

    public void reader() {
        synchronized (this) {
            int r = sync;
        }
        int r1 = a; /* ? */
        int r2 = b; /* ? */
    }

You got your answer by now, and to be fair, everyone here is correct, I just want to add one important rule.你现在已经得到了答案,公平地说,这里的每个人都是正确的,我只想添加一个重要的规则。 It's called "happens-before consistency", which goes like this:它被称为“在一致性之前发生”,它是这样的:

  • reads either see the latest write in happens-before order, or any other write not tied in happens-before order, thus a data-race.读取要么看到发生在发生前顺序中的最新写入,要么看到任何其他未按照发生前发生顺序写入的写入,因此是数据竞争。

So while the accepted answer is indeed correct, it should mention that in order for a happens-before edge to be created between (3) and (4) , (4) has to observe the write that (3) did.因此,虽然公认的答案确实是正确的,但应该提到,为了在(3)(4)之间创建发生前的边缘, (4)必须观察(3)所做的写入。

In your example:在您的示例中:

public void reader() {
   synchronized (this) {
      int r = sync;
   }
   int r1 = a; /* ? */
   int r2 = b; /* ? */

}

it means that int r = sync;这意味着int r = sync; is not correct, you need to assert that you actually saw sync to be 1 (you have observed the write).不正确,您需要断言您实际上看到sync1 (您已观察到写入)。 For example, this would create the needed edge:例如,这将创建所需的边缘:

if (sync == 1) {
    // guaranteed that r1=5 and r2=7
    int r1 = a;
    int r2 = b;
}

Does JMM guarantee the visibility of a synchronized write to the variable that is read in the other thread after a synchronized block? JMM 是否保证在synchronized块之后对在另一个线程中读取的变量进行synchronized写入的可见性?

Can I use a synchronized happens-before guarantee the same way?我可以以相同的方式使用同步发生之前的保证吗?

Yes and yes.是的,是的。

synchronized and volatile give the same visibility guarantees. synchronizedvolatile提供了相同的可见性保证。

In fact, a volatile variable behaves as if each read and write of this variable happens in its own tiny synchronized block.事实上,一个volatile变量的行为就好像这个变量的每次读取和写入都发生在它自己的小synchronized块中。

In the JLS terms:JLS条款中:

  • the visibility is guaranteed by the happens-before relation:可见性由happens-before关系保证:

    Informally, a read r is allowed to see the result of a write w if there is no happens-before ordering to prevent that read.非正式地,如果没有发生之前的命令以防止读取,则允许读取r查看写入w的结果。

  • volatile and synchronized are some of the ways to establish the happens-before relation: volatilesynchronized是建立前发生关系的一些方法:
    • An unlock on a monitor happens-before every subsequent lock on that monitor.监视器上的解锁发生在该监视器上的每个后续锁定之前
    • A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.对 volatile 字段(第 8.3.1.4 节)的写入发生在对该字段的每次后续读取之前
    • ... ...

The quote from Safe Publication and Safe Initialization in Java describes the case when: Java 中安全发布和安全初始化的引用描述了以下情况:

  • an object is initialized in a synchronized block in one thread object 在一个线程的synchronized块中初始化
  • the object and is read without synchronized blocks in another thread. object 并且在另一个线程中没有synchronized块的情况下读取。

In this situation the reader thread might see the object in the middle of initialization.在这种情况下,阅读器线程可能会在初始化过程中看到 object。

What's important to note here is that happens-before is a transitive relation.这里需要注意的重要一点是, happens-before是一个传递关系。 So if A happens-before B and B happens-before C , we can safely conclude that A happens-before C .因此,如果A发生在BB发生在C之前,我们可以安全地得出A发生在C之前的结论。

Now let's look at the code in question (I've added comments for clarity):现在让我们看看有问题的代码(为了清楚起见,我添加了注释):

public void writer() {
    a = 5; //1
    b = 7; //2
    synchronized (this) { 
        sync = 1;
    } //3
}

public void reader() {
    synchronized (this) { //4
        int r = sync;
    }
    int r1 = a; //5 
    int r2 = b; //6
}

We know that 1 happens-before 2 and 2 before 3 since they're executed by the same thread:我们知道1发生在2之前和2发生在3之前,因为它们是由同一个线程执行的:

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,则为 hb(x, y)。

We also know that 3 happens before 4 :我们也知道3发生在4之前:

An unlock action on monitor m synchronizes-with all subsequent lock actions on m (where "subsequent" is defined according to the synchronization order).监视器 m 上的解锁操作与 m 上的所有后续锁定操作同步(其中“后续”根据同步顺序定义)。

If an action x synchronizes-with a following action y, then we also have hb(x, y).如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。

Lastly, we know that 4 happens-before 5 and 5 before 6 since they're executed in the same thread.最后,我们知道4发生在5之前和5发生在6之前,因为它们在同一个线程中执行。

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,则为 hb(x, y)。

So we end up with a chain of happens-before relationships from 1 to 6. Consequently, 1 happens-before 6 .所以我们最终得到了从 1 到 6 的一系列发生之前的关系。因此, 1发生在6之前。

Does JMM guarantee the visibility of a synchronized write to the variable that is read in the other thread after a synchronized block? JMM 是否保证在同步块之后对在另一个线程中读取的变量进行同步写入的可见性?

Yes, for the reasons given in the other answer.是的,由于其他答案中给出的原因。

But there is a catch.但是有一个问题!

Let us assume that the reader call follows the writer call so that there is a happens before between the writer exiting its synchronized block and reader entering its block... and transitively it exiting the block and reading b .让我们假设reader调用跟在writer调用之后,因此在writer退出其同步块和 reader 进入其块之间之前发生了一个事件......并传递它退出块并读取b

The catch is when reader exits its block, there is no longer a guarantee of mutual exclusion on b .问题是当reader退出其块时,不再保证b上的互斥。 So suppose that another thread immediately acquires the mutex and modifies b .所以假设另一个线程立即获取互斥锁并修改b Since there is no HB chain connecting that write to b with the read in reader , there are no guarantees what the reader code will actually see that update... or the value that writer wrote previously.由于没有 HB 链写入b与 read in reader连接起来,因此无法保证reader代码实际看到更新...或writer之前写入的值。

In short, you need to consider more than HB relationships when reasoning about concurrent algorithms.简而言之,在推理并发算法时,需要考虑的不仅仅是HB 关系。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM