`

对于单例模式的一点想法

阅读更多
单例模式很普遍,对于Spring的实现机制不清楚,单就Java语言上的实现机制来讨论。
虽然简单,但要获得一个高性能且线程安全的单例确不简单。
最简单的、成熟的单例实现有如下两种:
1.
public static final Singleton INSTANCE=new Singleton();  

即在声明静态变量时就实例化。这种方法的问题是,不能传入构造参数从而动态的创建实例。
2.
public static synchronized Singleton getInstance(){...}  

即在方法上同步。这种方法的问题是,始终有同步的开销(虽然对很多应用来说这开销并不大,以致不需要考虑),而更理想的情况是,读操作不需要同步,只在创建实例时同步。
看上去更好的方法(但有问题!)是:
Double-checked synchronization,
如:
 private static Singleton INSTANCE;   
public static Singleton getInstance(){   
  if(INSTANCE==null){   
    synchronized(Singelton.class){   
      //Double checking   
      if(INSTANCE==null){   
        INSTANCE=new Singleton();   
      }   
    }   
  }   
}  

问题解释如下:
参考1:http://www.ibm.com/developerworks/java/library/j-dcl.html
参考2:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
在参考1中提到,out-of-order writes是原因,就是说INSTANCE=new Singleton();这行代码并不是一定按如下伪代码顺序进行的:
1.分配内存
2.调用构造器
3.赋值给INSTANCE

在有的JIT上会编译优化为:
1.分配内存
2.赋值给INSTANCE
3.调用构造器

这就是所谓的out-of-order writes。则问题会出在第2步:此时判断(INSTANCE==null)已经返回真了,但构造器还未调用完成,此时访问INSTANCE则会出现不可预料的问题。
以上都是简单的重复广为人知的知识,下面是我的补充:
在参考2中的"It will work for 32-bit primitive values"一节给了我启发,它提到对32位的原始类型的Double-checked locking是可以的,(我认为实际关键点在于
赋值操作是否是原子的)。既然对int的赋值是原子的,我们可以稍加改进,引入一个int hasInitialized:
private static int hasInitialized=0;   
private static Singleton INSTANCE;   
public static Singleton getInstance(){   
  if(hasInitialized==0){   
    synchronized(Singelton.class){   
      //Double checking   
      if(hasInitialized==0){   
        INSTANCE=new Singleton();   
        hasInitialized=1;   
      }   
    }   
  }   
} 


区别在于:
以hasInitialized==0来判断是否初始化完成,而在NSTANCE=new Singleton();之后才赋值以确认初始化完成。
这样不是既可保持高性能(绝大部分情况下没有锁,不进入需同步的块)、又可保证线程安全么?

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics