Java volatile、final和synchronized在安全发布方面的差异

Java volatile、final和synchronized在安全发布方面的差异,java,concurrency,Java,Concurrency,给定一个带有变量x的类a。变量x在类构造函数中设置: A() { x = 77; } 我们想将x发布到其他线程。考虑以下3种变量X线程安全(?)发布: 1) x是最终的 2) x是易变的 3) x在同步块中设置 synchronized(someLock) { A a = new A(); a.x = 77; } Thread2只打印x: System.out.println(a.x); 问题是:是否可以观察Thread2打印的“0”?或者JMM保证在所有3种情况下都会打印“77”或抛

给定一个带有变量x的类a。变量x在类构造函数中设置:

A() {
x = 77;
}
我们想将x发布到其他线程。考虑以下3种变量X线程安全(?)发布:

1) x是最终的

2) x是易变的

3) x在同步块中设置

synchronized(someLock) {
A a  = new A();
a.x = 77;
}
Thread2只打印x:

 System.out.println(a.x);
问题是:是否可以观察Thread2打印的“0”?或者JMM保证在所有3种情况下都会打印“77”或抛出NPE?

我的答案来自,特别是第17章,它涉及内存可见性和并发性

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

  • 最后一场。我将引用上述第17.5章:

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

  • 易变的。我将再次引用JLS:

对易失性变量v的写入(§8.3.1.4)与任何线程对v的所有后续读取同步(其中“后续”是根据同步顺序定义的)

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

  • x在同步块中。这是一个棘手的问题。可能是也可能不是 可见。事实上,同步只会略微增加 很难解释这一点,因此我将放弃它,并解释是否 一个简单的局部变量是否会在这里出现。然后添加一个 关于同步的子句
根据定义,如果在读取和写入之间的关系之前发生了,则它保证是可见的。否则,您可能会看到一个未初始化的值。在关系发生之前,什么构成了
?JLS第17章再次规定了这一点,特别是:

  • 单线程中的操作顺序
  • 同步、锁定和波动性
  • 对象和线程创建
  • 一切都是可传递的
  • 更多信息,请阅读JLS
因此,可能存在两种情况:

A a = new A();
Thread t = new MyThread(a);
t.start();
其中
MyThread
保存并使用的实例。在这种情况下,线程是在
a
之后创建的,而
start()
是在创建之后调用的。因此,即使
x
是非易失性的,也可以保证可见性。但是,不能保证对
x
的进一步更改的可见性

情景2:

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

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


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

你不能真正发布“int”-你发布“a”。不,只有final才能真正保证安全部分(volatile相当尴尬,但它不是)。案例2非常学术,请参见@kelveng,即使在臭名昭著的roach汽车旅馆之后,商店也可以在易变商店
x
之前移动到
a
的不安全位置。。。但是我记得去年夏天的激烈讨论。声明
x
volatile并不能确保看到A的不安全发布的正确值,因为编译器可以自由地重新排序
A
的发布并设置
x
的值。同步也不能保证任何事情。@同步上的bestsss-假定OP像他那样将整个对象创建和分配放入同步块,并且读取也在该监视器上-确实如此,但读取中没有显式同步-不作任何保证。我不确定第二种情况,我认为在构造函数完全完成之前,线程不能给出write对象引用。不过我可能弄错了。奥多斯,这就是我所想的:volatile write和volatile read之间有一个HB,但是我们不知道读线程什么时候会看到对对象的引用。它可能会看到对未初始化对象的引用。我想知道如果我们使“a”字段不稳定会发生什么。即易失性A=新A();这是否保证了a.x变量的安全发布?或者编译器仍然可以对行进行重新排序,并将单位化对象写入易失性字段?@Dymytry,确保它是安全的。基本上从不做不安全的出版物。至于重新排序,编译器和CPU都不允许将易失性写操作移过任何写操作/同步块。但是,可以将正常写入排序为之前的易失性写入。(所谓安全,我指的是所有3种方法都是安全的)