在 Java 中安全发布不可变对象
Safe publication of immutable objects in Java
我想了解是否需要 volatile
来发布不可变对象。
例如,假设我们有一个不可变对象A
:
// class A is immutable
class A {
final int field1;
final int field2;
public A(int f1, int f2) {
field1 = f1;
field2 = f2;
}
}
然后我们有一个从不同线程访问的class B
。它持有对 class A
:
对象的引用
// class B publishes object of class A through a public filed
class B {
private /* volatile? */ A toShare;
// this getter might be called from different threads
public A getA(){
return toShare;
}
// this might be called from different threads
public void setA(num1, num2) {
toShare = new A(num1, num2);
}
}
根据我的阅读,不可变对象似乎可以通过任何方式安全发布,所以这是否意味着我们不需要将 toShare
声明为 [=13] =] 以确保其内存可见性?
不,您不保证运行您会看到共享数据的 toShare
字段的所有更新。这是因为您的共享数据不使用任何同步结构来保证其可见性或通过它跨线程访问的引用的可见性。这使得它可以在编译器和硬件级别进行大量优化。
您可以安全地更改您的 toShare
字段以引用 String
(对于您的所有用途而言它也是不可变的)并且您可能(并且正确地)对其更新可见性感到更加不安.
Here 您可以看到我创建的一个基本示例,它可以显示更新是如何丢失的,而无需任何其他措施来发布对不可变对象引用的更改。我已经 运行 它在 JDK 8u65 和 Intel® Core™ i5-2557M 上使用 -server
JVM 标志,忽略可能抛出的 NullPointerException
并看到以下结果:
- 如果
safe
不是 volatile
,第二个线程不会终止,因为它没有看到第一个线程所做的许多更改
控制台输出:
[T1] Shared data visible here is 2147483647
- 当
safe
更改为 volatile
时,第二个线程与第一个线程一起终止
控制台输出:
[T1] Shared data visible here is 2147483647
[T2] Last read value here is 2147483646
[T2] Shared data visible here is 2147483647
P.S。还有一个问题 - 如果 sharedData
(而不是 safe
)变成 volatile
会怎样?根据 JMM 会发生什么?
答案为NO,需要使用volatile
或其他方式(例如在签名get和set中都添加synchronized
关键字) 形成 Happens/Before 边。 Final 字段语义仅保证如果有人看到指向 class 实例的指针,则所有 final 字段在完成时根据构造函数设置其值:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
这并没有说明引用本身的可见性。由于您的示例使用非最终字段
private A toShare;
您必须注意 volatile
或 synchronized
部分或 java.util.concurrent.locks.Locks 或 AtomicReference 等字段的可见性以 initiate/guarantee 缓存同步。顺便说一句,关于决赛和安全出版的一些有用的东西 http://shipilev.net/blog/2014/safe-public-construction/
似乎 JMM 应该解决发布不可变对象的可见性问题,至少在并发实践 3.5.2 不可变对象和安全初始化 中是这样说的:
Because immutable objects are so important, the JavaMemory Model offers a special guarantee of initialization safety
for sharing immutable objects. As we've seen, that an object reference becomes visible to another thread does not
necessarily mean that the state of that object is visible to the consuming thread. In order to guarantee a consistent view
of the object's state, synchronization is needed.
Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the
object reference. For this guarantee of initialization safety to hold, all of the requirements for immutability must be met:
unmodifiable state, all fields are final, and proper construction.
Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is
not used to publish them.
下一章3.5.3 安全发布惯用语指出安全发布仅需要对于非不可变对象,使用以下方法:
- 静态初始化程序
- 在 volatile/final/AtomicReference
中存储引用
- 存储由锁保护的引用
我想了解是否需要 volatile
来发布不可变对象。
例如,假设我们有一个不可变对象A
:
// class A is immutable
class A {
final int field1;
final int field2;
public A(int f1, int f2) {
field1 = f1;
field2 = f2;
}
}
然后我们有一个从不同线程访问的class B
。它持有对 class A
:
// class B publishes object of class A through a public filed
class B {
private /* volatile? */ A toShare;
// this getter might be called from different threads
public A getA(){
return toShare;
}
// this might be called from different threads
public void setA(num1, num2) {
toShare = new A(num1, num2);
}
}
根据我的阅读,不可变对象似乎可以通过任何方式安全发布,所以这是否意味着我们不需要将 toShare
声明为 [=13] =] 以确保其内存可见性?
不,您不保证运行您会看到共享数据的 toShare
字段的所有更新。这是因为您的共享数据不使用任何同步结构来保证其可见性或通过它跨线程访问的引用的可见性。这使得它可以在编译器和硬件级别进行大量优化。
您可以安全地更改您的 toShare
字段以引用 String
(对于您的所有用途而言它也是不可变的)并且您可能(并且正确地)对其更新可见性感到更加不安.
Here 您可以看到我创建的一个基本示例,它可以显示更新是如何丢失的,而无需任何其他措施来发布对不可变对象引用的更改。我已经 运行 它在 JDK 8u65 和 Intel® Core™ i5-2557M 上使用 -server
JVM 标志,忽略可能抛出的 NullPointerException
并看到以下结果:
- 如果
safe
不是volatile
,第二个线程不会终止,因为它没有看到第一个线程所做的许多更改
控制台输出:
[T1] Shared data visible here is 2147483647
- 当
safe
更改为volatile
时,第二个线程与第一个线程一起终止
控制台输出:
[T1] Shared data visible here is 2147483647
[T2] Last read value here is 2147483646
[T2] Shared data visible here is 2147483647
P.S。还有一个问题 - 如果 sharedData
(而不是 safe
)变成 volatile
会怎样?根据 JMM 会发生什么?
答案为NO,需要使用volatile
或其他方式(例如在签名get和set中都添加synchronized
关键字) 形成 Happens/Before 边。 Final 字段语义仅保证如果有人看到指向 class 实例的指针,则所有 final 字段在完成时根据构造函数设置其值:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
这并没有说明引用本身的可见性。由于您的示例使用非最终字段
private A toShare;
您必须注意 volatile
或 synchronized
部分或 java.util.concurrent.locks.Locks 或 AtomicReference 等字段的可见性以 initiate/guarantee 缓存同步。顺便说一句,关于决赛和安全出版的一些有用的东西 http://shipilev.net/blog/2014/safe-public-construction/
似乎 JMM 应该解决发布不可变对象的可见性问题,至少在并发实践 3.5.2 不可变对象和安全初始化 中是这样说的:
Because immutable objects are so important, the JavaMemory Model offers a special guarantee of initialization safety for sharing immutable objects. As we've seen, that an object reference becomes visible to another thread does not necessarily mean that the state of that object is visible to the consuming thread. In order to guarantee a consistent view of the object's state, synchronization is needed.
Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the object reference. For this guarantee of initialization safety to hold, all of the requirements for immutability must be met: unmodifiable state, all fields are final, and proper construction.
Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.
下一章3.5.3 安全发布惯用语指出安全发布仅需要对于非不可变对象,使用以下方法:
- 静态初始化程序
- 在 volatile/final/AtomicReference 中存储引用
- 存储由锁保护的引用