volatile,final和synchronized之间安全发布的差异

Mia*_*ach 5 java concurrency

给定具有变量x的A类.变量x在类构造函数中设置:

A() {
x = 77;
}
Run Code Online (Sandbox Code Playgroud)

我们想将x发布到其他一些线程.考虑以下3个变量x线程安全(?)发布的情况:

1)x是最终的

2)x是易变的

3)x在同步块中设置

synchronized(someLock) {
A a  = new A();
a.x = 77;
}
Run Code Online (Sandbox Code Playgroud)

Thread2只打印x:

 System.out.println(a.x);
Run Code Online (Sandbox Code Playgroud)

问题是:是否可以观察到Thread2打印的'0'?或者JMM保证将打印'77'或者在所有3种情况下都会抛出NPE?

Ord*_*ous 3

我的答案来自互联网上最好的页面,特别是第 17 章,其中涉及内存可见性和并发性。

\n\n

我还假设您没有引用泄漏(即在对象构造函数完成之前您没有对该对象的引用)。

\n\n
    \n
  • 最后场。我将引用上面的内容,第 17.5 章:

    \n\n
    \n

    当对象的构造函数完成时,该对象被认为已完全初始化。仅在对象完全初始化后才能看到对该对象的引用的线程保证看到该对象的最终字段的正确初始化值。

    \n
  • \n
  • 易挥发的。再次,我将引用 JLS:

  • \n
\n\n
\n

对易失性变量 v (\xc2\xa78.3.1.4) 的写入与任何线程对 v 的所有后续读取进行同步(其中“后续”是根据同步顺序定义的)。

\n
\n\n

因此,假设您Thread在完成构造函数后访问了该对象,它将看到正确的值。请注意,这意味着您可能还需要创建一个 volatile。

\n\n
    \n
  • x 在同步块中。这是一个棘手的问题。它可能是可见的,也可能是不可见的。事实上,同步仅稍微增加了解释这一点的难度,因此我将放弃它并解释这里是否会看到简单的局部变量。然后添加一个关于同步的\n子句。
  • \n
\n\n

Happens-before根据定义,如果读和写之间存在关系,则保证可见。否则,您可能会看到未初始化的值。什么构成Happens-before关系?JLS 第 17 章再次详细说明了这一点,特别是:

\n\n
    \n
  • 单线程中的操作顺序。
  • \n
  • 同步、锁定和波动性。
  • \n
  • 对象和线程的创建。
  • \n
  • 一切都是传递性的。
  • \n
  • 更多内容,请阅读 JLS
  • \n
\n\n

因此可能有两种情况:

\n\n
A a = new A();\nThread t = new MyThread(a);\nt.start();\n
Run Code Online (Sandbox Code Playgroud)\n\n

WhereMyThread保存 A 的实例并使用它。在这种情况下,线程是在之后创建的a,并且start()在创建之后被调用。因此,可见性得到了保证,即使是x非易失性的。x但是,不能保证进一步更改的可见性。

\n\n

情况2:

\n\n

这编码起来有点困难,但是:
\nMain 创建两个线程并立即启动它们,并且有一个 A 类型的非易失性字段。
\nThreadA 创建 A 并将其写入共享字段。
\nThreadB 循环一段时间,直到该字段被填充,然后打印出 x。

\n\n

在这种情况下,即使在写入 x 和写入共享字段之间存在 HB,但在读取和写入共享字段之间不存在 HB。因此,无法保证写入 x 的可见性。

\n\n

正如所承诺的 - 如果您在这两种情况中的任何一种情况下在对 x 的写入周围放置一个同步块,它都不会影响结果,因为监视器上没有其他锁定。锁定和解锁同一监视器会创建同步操作,从而创建 HB 关系。

\n