当两个线程同时调用“getInstance()”时,Singleton的行为如何?保护它的最佳做法是什么?
答案 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
}
答案 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
}
}