How does Singleton behave when two threads call the “getInstance()” at the same time?

扶醉桌前 提交于 2019-12-22 06:47:42

问题


How does Singleton behave when two threads call the "getInstance()" at the same time? What are the best practices to protect it?


回答1:


Firstly, two threads can't call the method at the "same time" - one will be deemed to call it first... called a "race condition".

Next, any properly implemented singleton will handle a race condition cleanly. IMHO, this is the cleanest way to implement a thread-safe singleton without synchronization:

public class MySingleton {
    private static class Holder {
        static final MySingleton INSTANCE = new MySingleton ();
    }

    public static MySingleton getInstance() {
        return Holder.INSTANCE;
    }
    // rest of class omitted
}

This is called the initialization-on-demand holder idiom.




回答2:


This is only an issue at all if you use lazy initialization on the singleton. If you use eager initialization then the JVM guarantees to sort it all out for you.

For lazy initialization you need to either synchronize (although you can make it volatile and use double-check locking to avoid synchronized blocks all the time) or embed it within an inner class where it is not lazy initialized.

peter.petrov's answer now covers most of the options well, there is one final approach to thread safe lazy initialization though that is not covered and it is probably the neatest one.

public class Singleton {

  // Prevent anyone else creating me as I'm a singleton
  private Singleton() {
  }

  // Hold the reference to the singleton instance inside a static inner class
  private static class SingletonHolder {
    static Singleton instance = new Singleton();    
  }

  // Return the reference from inside the inner class
  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }

}

Java does lazy loading on classes, they are only loaded when first accessed. This applies to inner classes too...




回答3:


1) If you want lazy init, I think a good practice is to synchronize the getInstance body on a private static final Object instance which is member of the same class (you may name it LOCK e.g.).

2) If you don't need lazy init you can just instantiate your singleton instance at class load time. Then there's no need of any synchronization in getInstance.

Sample of 1) without using DCL (double-checked locking)

Note 1: This one avoids the complexity of using DCL by paying some extra price with respect to performance.
Note 2: This version is OK on JDK < 5 as well as on JDK >= 5.

public class Singleton {

    private static final Object LOCK = new Object();
    private static Singleton instance = null;

    public static Singleton getInstance(){
        synchronized(LOCK){
            if (instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }

    private Singleton(){
        // code to init this
    }


}

Sample of 1) using DCL

Note 1: This is OK on JDK >= 5 but not on JDK < 5.
Note 2: Note the volatile keyword used, this is important.

public class Singleton {

    private static final Object LOCK = new Object();
    private static volatile Singleton instance = null;

    public static Singleton getInstance(){
        if (instance == null){
            synchronized(LOCK){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    private Singleton(){
        // code to init this
    }


}

Sample of 2)

Note 1: This is the most simple version.
Note 2: Works on any JDK version.

public class Singleton {

    private static Singleton instance = new Singleton();

    public static Singleton getInstance(){
        return instance;
    }

    private Singleton(){
        // code to init this
    }

}

References:

1) Older JDK versions (JDK < 5)
http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html

2) More recent updates on DCL
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html




回答4:


public class Singleton{

      private static class Holder {
          static final Singleton INSTANCE = new Singleton();
      }

      public static Singleton getInstance() {
          return Holder.INSTANCE;
      }

} 

When the getInstance method is invoked for the first time, it reads Holder.INSTANCE for the first time, causing the Holder class to get initialized. The beauty of this idiom is that the getInstance method is not synchronized and performs only a field access.This is called lazy initialization. You might think that Holder class should also be loaded by the class loader when Singleton class is loaded because Holder class is static. Loading a top-level class does not automatically load any nested types within,Unless there's some other initialization that occurs during the top-level initialization, like if your top-level class has a static field that needs to be initialized with a reference to an instance of the nested class.




回答5:


Synchronize access of getInstance.

Class TestSingleton {

  private static volatile TestSingleton singletonObj = null;   

  private TestSingleton (){    // make constructor private
  }

  public static getInstance(){
       TestSingleton obj = singletonObj ;
       if(obj == null) {
       synchronized(lock) {    // while we were waiting for the lock, another 
             obj = instance;       // thread may have instantiated the object
             if(obj == null) {  
                obj = new TestSingleton();
                instance = obj ;
             }
        }
   }
  public doSomeWork(){    // implementation
  }

}



来源:https://stackoverflow.com/questions/20940715/how-does-singleton-behave-when-two-threads-call-the-getinstance-at-the-same

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!