Java 多线程纠错 - 线程安全单例



首先:是的,我知道一般来说,在 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(); 
} 

因错误而删除,保留空答案以供讨论。哇,生活和学习!

最新更新