Java 什么时候引用需要是原子的?
在Java中以原子方式分配引用意味着什么Java 什么时候引用需要是原子的?,java,multithreading,Java,Multithreading,在Java中以原子方式分配引用意味着什么 我确实理解长双精度的含义,也就是说:一个线程可以看到部分构造的数字 但对于一个我不理解的对象,赋值并不意味着拷贝只是指向内存中的地址 那么,如果引用分配在Java中不是原子的,那么会出现什么问题呢?我假设您是在询问原子引用的AtomicReference 其思想是,如果两个或多个线程读取或更新引用类型变量的值,则可能会得到意外的结果。例如,假设每个线程检查某个引用类型变量是否为null,如果为null,则创建该类型的实例并更新该引用变量 如果两个线程
- 我确实理解长双精度的含义,也就是说:一个线程可以看到部分构造的数字
- 但对于一个我不理解的对象,赋值并不意味着拷贝只是指向内存中的地址
那么,如果引用分配在Java中不是原子的,那么会出现什么问题呢?我假设您是在询问原子引用的
AtomicReference
其思想是,如果两个或多个线程读取或更新引用类型变量的值,则可能会得到意外的结果。例如,假设每个线程检查某个引用类型变量是否为null,如果为null,则创建该类型的实例并更新该引用变量
如果两个线程同时看到变量为null,这可能会导致创建两个实例。如果您的代码依赖于使用该变量引用的同一实例的所有线程,那么您将遇到麻烦
现在,如果使用AtomicReference
,则可以通过使用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
}
static AtomicReference=new AtomicReference();
...
//某线程的代码
MyClass obj=ref.get();
if(obj==null){
obj=新的MyClass();
如果(!ref.compareAndSet(null,obj))//尝试将原子引用设置为新值
//除非它仍然为空
obj=ref.get();//如果其他线程设法将其设置在当前线程之前,
//获取由另一个线程创建的实例
}
让我们考虑经典的双检查锁定示例来理解为什么引用需要是原子的:
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...
}
让我们考虑2个线程,将调用<代码> GeTelpP<代码>方法:
线程1执行行号1,并发现结果
为空
线程1在第2行获得类级锁
线程1在第3行发现结果为null
线程1开始实例化一个新的Helper
当线程1还在第4行实例化一个新的助手时,线程2执行第1行
第4步和第5步是可能出现不一致的地方。在步骤4中,对象可能没有完全实例化,但结果
变量中已经印有部分创建的帮助对象的地址。如果步骤5在助手
对象完全初始化之前执行一纳秒,线程2将看到结果
引用不是null
,并可能返回对部分创建的对象的引用
解决此问题的一种方法是将结果
标记为易失性
或使用原子引用
。也就是说,上述场景在现实世界中不太可能发生,并且有比使用双重检查锁定更好的方法来实现单例
使用原子引用实现双重检查锁定的示例:
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;
}
如果您有兴趣知道为什么步骤5会导致内存不一致,请查看答案(如注释中所建议的)这意味着您永远不会得到损坏的引用。假设您有以下类:
class MyClass {
Object obj = null;
}
内存中的obj
是一个空指针,通常是一个整数,如0x00000000
。然后假设在一个线程中有一个赋值:
this.obj = new Object();
假设在内存中分配了newobject()
,并具有类似0x12345678
的指针。引用原子性确保当您从另一个线程检查obj
时,您将拥有空指针(0x00000000
)或指向新对象的指针(0x12345678
)。但在任何情况下,您都无法获得不指向任何地方的部分指定引用(如0x12340000
)
这可能看起来很明显,但这种问题可能出现在诸如C
之类的低级语言中,具体取决于CPU体系结构和内存对齐方式。例如,如果指针未对齐并穿过缓存线,则可能无法同步更新。为了避免这种情况,Java虚拟机总是对齐指针,所以它们永远不会越过缓存线
因此,如果Java引用是非原子的,那么在取消引用从另一个线程写入的引用时,您可能得到的不是赋值前后引用的对象,而是随机内存位置(这可能导致分段错误、堆损坏或任何其他灾难).部分构造的参考。我发现这一行有点不正确。@ChetanKinger:现在好点了吗?我想指出的是,我们需要原子引用的原因是为了避免线程使用对部分构造对象的引用。答案应该是对部分构造对象的引用,而不是部分构造的引用。(在我看来)据我所知,64位系统上的指针通常是长的(8字节),而不是整数。无论如何,+1对于一个清晰的解释和上下文,我发现以下答案很有帮助:如果你能得到一个损坏的引用,那将是一个巨大的安全漏洞。@ChetanKinger-解决这个问题的一种方法是将结果标记为volatile或使用原子引用,但volatile只与变量本身的修改相关,不是它引用的对象,对吗?@user3320018原子引用的代码对引用的对象内部使用volatile
。它与对象的内容无关,对象的内容必须与re相同