首先:是的,我知道一般来说,在 Java 中做单例的最佳方法是使用 enum
s,但如果由于某种原因你需要对单例类进行子类化,你不能使用枚举,所以......
JavaWorld 的 David Geary 很久以前发表了一篇关于在 Java 中实现单例的文章。 他认为,对线程安全的单例实现进行以下优化是有问题的:
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized(Singleton.class)
{
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
(详见:http://www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html?page=4#sthash.G8lzWOfT.dpuf(
Geary说,这种"双重检查锁定"优化
不能保证工作,因为编译器可以自由分配 在单一实例之前对单一实例成员变量的值 构造函数被调用。如果发生这种情况,线程 1 可能会被抢占 在分配单例引用之后,但在 单例已初始化,因此线程 2 可以返回对 未初始化的单一实例。
我的问题:以下更改是否会解决这个问题? 我已经开始阅读 Goetz 的 Java 并发书,似乎允许编译器在线程内操作洗牌,所以我不太有信心......尽管如此,在我看来,singleton = temp;
是原子操作,在这种情况下,我认为它应该。 请解释一下。
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized(Singleton.class)
{
if(singleton == null) {
Singleton temp = new Singleton();
singleton = temp;
}
}
}
return singleton;
}
第二个代码与第一个代码按顺序一致(它们在单线程环境中严格等效(,并且不会引入任何其他内存同步点。
所以是的,编译器被授权重写第二个代码并将其转换为第一个代码,这意味着它也不安全。
singleton = temp;
是原子的这一事实在这里无济于事。这仅意味着单例为空或与 temp 具有相同的引用。但这并不排除 temp/singleton 指向"非构造"对象。
Java 内存模型在发生前 (HB( 关系方面工作。在这两个代码中,只有一个 hb:同步块的退出 hb 随后进入该块。 if (singleton == null)
不与singleton=…
共享任何 HB 关系,因此可以进行重新排序。
底线是,修复它的唯一方法是在两个语句之间引入 hb:例如,通过在同步块内移动 if 或通过标记单例易失性。
答案取决于编译器可以应用于第二个代码的优化(这意味着第二个代码可以通过编译器转换为第一个代码(。您可以使用AtomicReference编写代码,这样可以避免此问题:
private static AtomicReference<Singleton> singleton = new AtomicReference<Singleton>(null);
...
public static Singleton getInstance()
{
if (singleton.get() == null)
{
synchronized(Singleton.class)
{
if(singleton.get() == null) {
singleton.compareAndSet(null, new Singleton());
}
}
}
return singleton.get();
}
因错误而删除,保留空答案以供讨论。哇,生活和学习!