首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >同步块后读取的可见性

同步块后读取的可见性
EN

Stack Overflow用户
提问于 2022-08-05 14:48:30
回答 4查看 124关注 0票数 1

JMM是否保证synchronized写入到在synchronized块之后在另一个线程中读取的变量的可见性?我的意思是:

代码语言:javascript
复制
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保证监视器上的解锁发生--在该监视器上的每个后续锁之前。但我不确定它是否只与synchronized块体有关。

最近我遇到了来自Aleksey Shipil v- Java中的安全发布和安全初始化的这篇文章。上面写着:

请注意,在不安全的DCL存储中执行是如何没有帮助的,与外行人所认为的相反,它以某种方式神奇地“刷新缓存”或诸如此类。在读取受保护状态时,如果没有配对锁,则无法保证在受锁保护的写入之前看到写入。

这就是我问自己这个问题的原因。我在JLS上找不到答案。

换个说法吧。有时,您正在依赖volatile --在像这样的保证之前:

代码语言:javascript
复制
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 */
    }
}

您肯定会看到这两种写操作,因为在同一线程中的顺序操作在发生之前和发生之前都有支持,然后才是传递性的。

我是否可以使用发生的synchronized -在以同样的方式保证之前?甚至可能是这样(我已经将sync变量设置为禁止编译器/JVM删除否则为空的synchronized块):

代码语言:javascript
复制
    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; /* ? */
    }
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2022-08-06 01:52:45

这里需要注意的是,在此之前发生的是传递的关系。因此,如果A发生--在BB发生之前--在C之前,我们可以安全地得出结论,A发生在C之前。

现在,让我们看一下所讨论的代码(为了清楚起见,我添加了注释):

代码语言:javascript
复制
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
}

我们知道1发生在2之前,23之前,因为它们是由同一个线程执行的:

如果x和y是同一个线程的动作,x以程序顺序出现在y之前,则hb(x,y)。

我们也知道3发生在4之前

监视器m上的解锁操作同步-与m上的所有后续锁操作同步(其中“后继”根据同步顺序定义)。如果一个动作x与以下动作y同步,那么我们也有hb(x,y)。

最后,我们知道4发生在5之前,56之前,因为它们是在同一个线程中执行的。

如果x和y是同一个线程的动作,x以程序顺序出现在y之前,则hb(x,y)。

因此,我们最终会有一系列的事情发生--在关系从1到6之前。因此,1发生在6之前。

票数 1
EN

Stack Overflow用户

发布于 2022-08-16 07:42:20

你现在已经得到了答案,公平地说,这里的每个人都是正确的,我只想补充一条重要的规则。它被称为“在一致性之前发生”,它是这样的:

  • 读取的内容要么是在顺序之前看到最新的写入,要么是在顺序之前看到没有绑定的任何其他写入,因此是数据竞争。

因此,虽然公认的答案确实是正确的,但应该提到,为了在(3)(4)之间创建边界之前,(4)必须遵守(3)所做的写。

在你的例子中:

代码语言:javascript
复制
public void reader() {
   synchronized (this) {
      int r = sync;
   }
   int r1 = a; /* ? */
   int r2 = b; /* ? */

}

这意味着int r = sync;是不正确的,您需要断言您实际上看到了sync1 (您已经观察到了写入)。例如,这将创建所需的边缘:

代码语言:javascript
复制
if (sync == 1) {
    // guaranteed that r1=5 and r2=7
    int r1 = a;
    int r2 = b;
}
票数 2
EN

Stack Overflow用户

发布于 2022-08-05 23:19:42

JMM是否保证synchronized写入到在synchronized块之后在另一个线程中读取的变量的可见性?在保证同样的方式之前,我可以使用同步发生吗?

是的,是的。

synchronizedvolatile提供了相同的可见性保证。

实际上,volatile变量的行为就好像每个变量的读和写都发生在它自己的小synchronized块中。

JLS术语来说:

  • 可见性是由发生之前的关系:非正式地,读r允许查看写w的结果,如果没有发生-之前排序,以防止读取。
  • volatilesynchronized是建立发生前关系的一些方法:
    • 监视器上的解锁发生在该监视器的每个后续锁之前。
    • 对易失性字段(§8.3.1.4)的写入发生在该字段的每一次后续读取之前。
    • ..。

引用Java中的安全发布和安全初始化的话描述了以下情况:

  • 对象在一个线程中的synchronized块中初始化。
  • 对象是在另一个线程中没有 synchronized块的情况下读取synchronized的。

在这种情况下,读取器线程可能会看到初始化过程中的对象。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73251592

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档