单例模式

来源:互联网 发布:怎么限制电脑软件联网 编辑:程序博客网 时间:2024/06/05 09:52
单例模式的双检锁写法
  1. public class DoubleCheckedLock {
  2. private static DoubleCheckedLock instance;
  3. public static DoubleCheckedLock getInstance() {
  4. if (instance == null) {
  5. synchronized (DoubleCheckedLock.class) {
  6. if(instance==null){
  7. instance=new DoubleCheckedLock();
  8. }
  9. }
  10. }
  11. return instance;
  12. }
  13. }
双检锁写法的问题
双检锁机制确实解决了多线程并行中并不会出现重复new对象,并且实现了懒加载, 但是由于编译器中的优化:指令重排序,导致了instance=new DoubleCheckedLock();这行代码在编译后的不确定性,比如,某一种编译器编译后该代码的步骤为(指令重排序后的步骤):
1. instance = 给新的实例分配内存
2. 调用Singleton的构造函数来初始化instance的成员变量
如果有如下的场景:线程A和线程B调用getInstance,线程A先进入,在执行到步骤1的时候被强行终止了。然后B进入,B看到的是instance已经不是null了(内存已经分配并且将地址赋值给了instance),于是就直接返回了instance,而此时的instance返回的是未经初始化的对象,instance中的字段都是缺省值,直接使用会造成错误。
当然也会有其他的编译器编译后的步骤的代码是这样的(正常步骤):
1. temp = 分配内存
2. 调用temp的构造函数
3. instance = temp 
如果编译后的步骤是这样的话双检锁写法就没有问题了,但是真正的问题是根本无法知道编译器具体的编译到底是如何做的。这才是最蛋疼的地方。即不能保证双检锁写法在所有的编译器上都有正确的结果
使用volatile来禁止指令重排序优化可以吗?
即使用volatile来修饰instance变量来禁止指令重排序
  1. public class DoubleCheckedLock {
  2. private static volatile DoubleCheckedLock instance;
  3. public static DoubleCheckedLock getInstance() {
  4. if (instance == null) {
  5. synchronized (DoubleCheckedLock.class) {
  6. if(instance==null){
  7. instance=new DoubleCheckedLock();
  8. }
  9. }
  10. }
  11. return instance;
  12. }
  13. }
但是并不是所有的编译器都很好的实现了volatile禁止指令重排序的语义,volatile屏蔽指令重排序的语义在JDK 1.5才被完全的修复,此前的JDK中即使将变量声明为volatile也仍然不能完全避免重排序所导致的问题。由于这一点的原因,在JDK 1.5之前的Java中无法安全的使用DCL(双检锁)来实现单例模式
那么如何实现线程安全的Singleton呢?
1. 利用JVM的类加载和静态变量初始化特征来创建Singleton实例
  1. class DoubleCheckedLock {
  2. private static DoubleCheckedLock instance = new DoubleCheckedLock();
  3. private DoubleCheckedLock() {
  4. }
  5. public DoubleCheckedLock getInstance() {
  6. return instance;
  7. }
  8. }
因为static字段在类加载时进行初始化, 所以在加载该类时创建该类的实例并赋给instance字段, 并且classLoader将会保证直到类被完全加载并被初始化时才会可见。
2. 利用在方法上添加synchronized关键字来实现线程安全的单例模式
  1. class DoubleCheckedLock {
  2. private static DoubleCheckedLock instance = null;
  3. private DoubleCheckedLock() {
  4. }
  5. public synchronized DoubleCheckedLock getInstance() {
  6. if (instance == null)
  7. instance = new DoubleCheckedLock();
  8. return instance;
  9. }
  10. }
虽然这种方式保证了线程的安全,但是在性能上该方法是比较低的,原因在于对整个getInstance()方法都是同步的,限定了访问的速度。
3. 最优方式:使用静态内部类来实现线程安全的单例模式
  1. class StaticSingleton{
  2. private StaticSingleton() {
  3. }
  4. private static class SingletonHolder{
  5. private static StaticSingleton instance = new StaticSingleton();
  6. }
  7. public static StaticSingleton getInstance() {
  8. return SingletonHolder.instance;
  9. }
  10. }
当StaticSingleton被加载时,其内部类并不会被初始化,所以实例也不会被初始化。而当getInstance方法被调用时,才会加载SingletonHolder,从而初始化了instance。同时, 由于实例的建立是在类加载时完成的,天生对多线程友好,所以getInstance方法不需要使用同步关键字(因为类加载自身处理了多线程环境下的同步问题)。
0 0
原创粉丝点击