简体   繁体   中英

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...

David Geary at JavaWorld published an article a long time ago on implementing singletons in 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 )

Geary says that this 'double-checked locking' optimization

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. 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.

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; 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; is atomic doesn't help here. It only means that singleton is either null or holds the same reference as temp. But that does not preclude temp/singleton from pointing to a "non-constructed" object.

The Java Memory Model works in terms of happens-before (HB) relationships. In both codes there is only one hb: the exit of the synchronized block hb a subsequent entry into that block. if (singleton == null) does not share any hb relationship with singleton=… so the reordering can happen.

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.

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:

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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