Java 中的原子引用分配是什么意思?
- 我理解 long 和 double 原子分配的含义:线程可以看到部分构造的数字,
- 但是对于对象,我不理解,因为分配并不意味着复制,只是指向内存中的地址。
如果 Java 中的引用分配不是原子性的,会有什么问题呢?
Java 中的原子引用分配是什么意思?
如果 Java 中的引用分配不是原子性的,会有什么问题呢?
这意味着您将永远不会收到损坏的引用。假设您有以下类:
class MyClass {
Object obj = null;
}
在内存中,obj
是一个空指针,通常它是一个类似于 0x00000000
的整数。 然后假设你有一个赋值语句在一个线程中:
this.obj = new Object();
假设在内存中为new Object()
分配了一个指针,如0x12345678
。引用原子性确保当您从另一个线程检查obj
时,您将获得一个空指针(0x00000000
)或指向新对象的指针(0x12345678
)。但是在任何情况下,您都无法获得部分分配的引用(例如指向不存在的0x12340000
)。
这可能看起来很明显,但是在CPU架构和内存对齐方面,此类问题可能会出现在低级语言(如C
)。例如,如果指针未对齐并且跨越缓存行,则可能无法同步更新它。为避免这种情况,Java虚拟机始终对齐指针,因此它们永远不会跨越缓存行。
因此,如果Java引用非原子性,那么从另一个线程写入的引用进行解引用时,可能会得到之前或之后引用的对象而不是随机内存位置(这可能导致分段错误、堆损坏或任何其他灾难)。
让我们考虑经典的双重检查锁定示例,以理解为什么引用需要是原子的:
class Foo {
private Helper result;
public static Helper getHelper() {
if (result == null) {//1
synchronized(Foo.class) {//2
if (result == null) {//3
result = new Helper();//4
}
}
}
return result//5;
}
// other functions and members...
}
考虑两个线程将调用getHelper
方法:
result
为null
。result
为null
Helper
Helper
时,Thread-2执行第1行。步骤4和5是不一致可能出现的地方。有可能在第4步中,对象尚未完全实例化,但是result
变量已经具有部分创建的Helper
对象的地址。如果第5步在Helper
对象完全初始化之前执行,即使只有一纳秒,Thread-2也会看到result
引用不为null
,并可能返回对部分创建的对象的引用。
解决此问题的一种方法是将result
标记为volatile
或使用AtomicReference
。话虽如此,上述情况在实际世界中高度不太可能发生,并且有更好的方法来实现Singleton
而不是使用双重检查锁定。
这里提供了使用AtomicReference
实现双重检查锁定的示例:
private static AtomicReference instance = new AtomicReference();
public static AtomicReferenceSingleton getDefault() {
AtomicReferenceSingleton ars = instance.get();
if (ars == null) {
instance.compareAndSet(null,new AtomicReferenceSingleton());
ars = instance.get();
}
return ars;
}
volatile
来引用对象。这与对象的内容无关,必须在需要时执行相同操作,或者使用适当的同步。 - pwesAtomicReference<V>
。AtomicReference<V>
,则可以通过使用compareAndSet(V expect, V update)
方法来解决此问题。因此,仅当其他线程没有比它更快时,线程才会更新变量。static AtomicReference<MyClass> ref = new AtomicReference<> ();
...
// code of some thread
MyClass obj = ref.get();
if (obj == null) {
obj = new MyClass();
if (!ref.compareAndSet (null, obj)) // try to set the atomic reference to a new value
// only if it's still null
obj = ref.get(); // if some other thread managed to set it before the current thread,
// get the instance created by that other thread
}
MyClass
对象,这可能是不希望的。在内部使用普通的同步机制会有所帮助。 - pwes