3 回答

TA贡献1831条经验 获得超9个赞
如果没有同步,你的代码肯定会被破坏,因为 2 个线程可能会看到 instance 的值为 null,并且都将执行初始化(考虑在每一行进行上下文切换,看看会发生什么。
除此之外,即使同步双重检查锁定(DCL)在过去在 Java 中也被认为是坏的,因为在非同步运行时,第二个线程可能会以不同的顺序进行初始化操作。你可以通过添加一个局部变量来修复你的代码,并在你想要读取它时将 volatile 加载到其中:
public static Instance getInstance() {
Instance tmp = instance;
if (tmp == null) {
synchronized(Instance.class) {
Instance tmp = instance;
if (tmp == null) {
instance = new Instance();
}
}
}
return instance;
}
但更安全的解决方案是使用 ClassLoader 作为您的同步机制,并且还允许您在每次访问单例时停止使用慢速 volatile 访问:
public class Instance {
private static class Lazy {
private static Instance INSTANCE = new Instance();
}
public static Instance getInstance() {
return Lazy.INSTANCE;
}
}
INSTANCE 只有在第一个线程进入时才会被初始化 getInstance()

TA贡献1893条经验 获得超10个赞
是的,确实:易失性读取可能会以这样的方式发生冲突,即两个线程将看到引用的空值并执行双重实例化。
您还需要双括号初始化和 volatile。那是因为当instance
变为非 null 时,您在读取它之前不会在任何东西上同步其他线程 - 首先if
只是让它们进一步返回unsynchronized
值(即使初始化线程还没有转义同步块),这可能导致由于缺乏同步,后续线程读取未初始化变量。同步工作需要每个线程访问它所管理的数据来执行,DCL 在初始化后忽略了同步,这是错误的。这就是为什么您需要额外的 volatile 才能使 DCL 工作,然后 volatile 将确保您读取初始化值。
没有处理器缓存分离这样的东西,读取和写入是立即可见的,但是有指令重新排列,因此有利于优化处理器如果不需要立即调用它们的结果,则可以稍后调用一些指令。同步和 volatile 的全部意义在于不要重新排列访问它们的线程的指令顺序。这样,如果某事已同步并在代码中声明为已完成,则它确实已完成并且其他线程可以安全地访问它。这就是在保证之前发生的全部意义。
总结一下:没有适当的同步处理器可以将引用初始化instance
为非空,但instance
不能在内部完全初始化,因此后续线程读取它可能会读取未初始化的对象并因此行为错误。

TA贡献1887条经验 获得超5个赞
考虑到这段代码。
private volatile static Instance instance;
public static Instance getInstance() {
if (this.instance == null) {
this.instance = new Instance();
}
return this.instance;
}
从你的问题:
Will the class get instantiated only once? Can volatile reads clash in such way that two threads will see null value of the reference and double instantiation will be performed?
在 JMM 的保证之外,易失性读取不能以这种方式发生冲突。但是,如果多个线程在 if 之后但在开始实例化 volatile 变量之前交换,您仍然可以得到两个实例。
if (this.instance == null) {
// if threads swap out here you get multiple instances
this.instance = new Instance();
}
为了确保上述场景不会发生,你必须使用双重检查锁定
if (this.instance == null) {
// threads can swap out here (after first if but before synchronized)
synchronized(Instance.class) {
if (this.instance == null) {
// but only one thread will get here
this.instance = new Instance();
}
}
}
请注意,这里必须考虑两个方面。
原子性:我们需要确保第二个 if 和实例化以原子方式发生(这就是我们需要synchronized块的原因)。
可见性:我们需要确保对实例变量的引用不会在不一致的状态下转义(这就是为什么我们需要volatile实例变量的声明以利用 JMM 在保证之前发生)。
添加回答
举报