简体   繁体   English

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

[英]Java multithreading error correction - threadsafe singleton

FIRST OFF: Yes, I know that the best way in general to do singletons in Java is with enum s, but if for some reason you need to subclass a singleton class, you can't use enums, so... 首先关闭:是的,我知道在Java中使用单例的最佳方法是使用enum ,但如果由于某种原因你需要子类化单例类,则不能使用枚举,所以...

David Geary at JavaWorld published an article a long time ago on implementing singletons in Java. JavaWorld的David Geary很久以前发表了一篇关于用Java实现单例的文章。 He argued that the following optimization to a thread-safe singleton implementation is problematic: 他认为对线程安全的单例实现的以下优化是有问题的:

public static Singleton getInstance() 
{ 
    if (singleton == null) 
    { 
       synchronized(Singleton.class) 
       { 
           if(singleton == null) {
              singleton = new Singleton();
           } 
       }
    }
    return singleton; 
} 

(See more at: http://www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html?page=4#sthash.G8lzWOfT.dpuf ) (详见: http//www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html?page=4#sthash.G8lzWOfT.dpuf

Geary says that this 'double-checked locking' optimization Geary表示这种“双重检查锁定”优化

is not guaranteed to work because the compiler is free to assign a value to the singleton member variable before the singleton's constructor is called. 不能保证工作,因为在调用单例的构造函数之前,编译器可以自由地为singleton成员变量赋值。 If that happens, Thread 1 can be preempted after the singleton reference has been assigned, but before the singleton is initialized, so Thread 2 can return a reference to an uninitialized singleton instance. 如果发生这种情况,可以在分配单例引用之后但在单例初始化之前抢占线程1,因此线程2可以返回对未初始化单例实例的引用。

My question: Is the following change going to fix that problem or not? 我的问题:以下更改是否可以解决该问题? I've started reading Goetz's Java concurrency book and it seems that the compiler is allowed to shuffle within-thread operations, so I am not quite confident...Still, it seems to me that singleton = temp; 我已经开始阅读Goetz的Java并发书了,似乎允许编译器在线程内操作进行随机播放,所以我不太自信......但在我看来, singleton = temp; is an atomic operation, in which case I think it should. 是一个原子操作,在这种情况下我认为它应该。 Please explain. 请解释。

public static Singleton getInstance() 
{ 
    if (singleton == null) 
    { 
       synchronized(Singleton.class) 
       { 
           if(singleton == null) {
              Singleton temp = new Singleton();
              singleton = temp;
           } 
       }
    }
    return singleton; 
} 

The second code is sequentially consistent with the first code (they are strictly equivalent in a single threaded environment) and does not introduce any additional memory synchronisation points. 第二个代码与第一个代码顺序一致(它们在单个线程环境中严格相同),并且不引入任何额外的内存同步点。

So yes, a compiler is authorised to rewrite the second code and turn it into the first one which means it is unsafe too. 所以是的,编译器被授权重写第二个代码并将其转换为第一个代码,这意味着它也是不安全的。

The fact that singleton = temp; singleton = temp;的事实singleton = temp; is atomic doesn't help here. 原子在这里没有帮助。 It only means that singleton is either null or holds the same reference as temp. 它只表示singleton为null或与temp保持相同的引用。 But that does not preclude temp/singleton from pointing to a "non-constructed" object. 但这并不妨碍temp / singleton指向“非构造”对象。

The Java Memory Model works in terms of happens-before (HB) relationships. Java内存模型适用于发生前(HB)关系。 In both codes there is only one hb: the exit of the synchronized block hb a subsequent entry into that block. 在两个代码中只有一个hb:同步块的退出hb后续进入该块。 if (singleton == null) does not share any hb relationship with singleton=… so the reordering can happen. if (singleton == null)不与singleton=…共享任何hb关系,因此可以进行重新排序。

The bottom line is that the only way to fix it is to introduce a hb between the two statements: by moving the if inside the synchronized block or by marking singleton volatile for example. 最重要的是,修复它的唯一方法是在两个语句之间引入一个hb:在同步块内移动if或者例如标记singleton volatile。

The answer depends on optimization which can be applied for the second code by compiler (it means that second one can be transformed to first one by compiler). 答案取决于可以由编译器应用于第二代码的优化(这意味着可以通过编译器将第二代码转换为第一代码)。 You can write the code using AtomicReference which will allow to avoid the problem: 您可以使用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(); 
} 

Deleted as wrong, keeping the empty answer for its discussions. 删除错误,为讨论保留空白答案。 Wow, live and learn! 哇,生活和学习!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM