当两个线程同时调用“getInstance()”时,Singleton如何表现?

时间:2014-01-05 23:42:44

标签: java design-patterns singleton

当两个线程同时调用“getInstance()”时,Singleton的行为如何?保护它的最佳做法是什么?

5 个答案:

答案 0 :(得分:3)

如果你对单例使用延迟初始化,这只是一个问题。如果您使用急切初始化,那么JVM会保证为您排序。

对于延迟初始化,您需要同步(尽管可以使其变为volatile并使用双重检查锁定以避免始终使用同步块)或将其嵌入到内部类中,而不是在其中进行惰性初始化。

peter.petrov的答案现在已经涵盖了大多数选项,有一种最终的线程安全延迟初始化方法虽然没有涵盖,但它可能是最好的。

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对类进行延迟加载,它们仅在首次访问时加载。这也适用于内部类......

答案 1 :(得分:2)

1)如果你想要延迟初始化,我认为一个好的做法是在私有静态最终的Object实例上同步getInstance主体,该实例是同一个类的成员(你可以将其命名为LOCK,例如。)。

2)如果你不需要lazy init,你可以在类加载时实例化你的单例实例。然后在getInstance中不需要任何同步。

1)样本,不使用DCL(双重检查锁定)

注1:这个通过支付额外的性能价格来避免使用DCL的复杂性。
注2:此版本在JDK上是正常的< 5以及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
    }


}

1)使用DCL的样本

注1:这在JDK> = 5上是正常的,但在JDK< 5.
注2:注意使用的volatile关键字,这很重要。

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
    }


}

样本2)

注1:这是最简单的版本 注2:适用于任何JDK版本。

public class Singleton {

    private static Singleton instance = new Singleton();

    public static Singleton getInstance(){
        return instance;
    }

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

}

<强>参考文献:

1)较旧的JDK版本(JDK <5)
http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html

2)有关DCL的最新更新
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

答案 2 :(得分:2)

首先,两个线程无法在“同一时间”调用该方法 - 一个将被视为首先调用它...称为“竞争条件”。

接下来,任何正确实施的单身人士都会干净地处理竞争条件。恕我直言,这是实现线程安全单例而不同步的最简洁方法

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

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

这称为initialization-on-demand holder idiom

答案 3 :(得分:1)

public class Singleton{

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

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

} 

第一次调用getInstance方法时,它首次读取Holder.INSTANCE,导致Holder类被初始化。这个习惯用法的优点在于getInstance方法不是同步的,只执行字段访问。这称为延迟初始化。您可能认为加载Singleton类时,类加载器也应该加载Holder类,因为Holder类是静态的。加载顶级类不会自动加载任何嵌套类型,除非在顶级初始化期间发生了一些其他初始化,例如,如果您的顶级类具有需要通过引用初始化的静态字段嵌套类的实例。

答案 4 :(得分:0)

同步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
  }

}