单例模式完全解析

本文将探讨单例模式的各种情况,并给出相应的建议。单例模式应该是设计模式中比较简单的一个,但是在多线程并发的环境下使用却是不那么简单了。
首先看最原始的单例模式。
1  package  xylz.study.singleton;
2 
3  public   class  Singleton {
4 
5       private   static  Singleton instance  =   null ;
6 
7       private  Singleton() {
8      }
9 
10       public   static  Singleton getInstance() {
11           if  (instance  ==   null ) {
12              instance  =   new  Singleton();
13          }
14           return  instance;
15      }
16  }
17 

显然这个写法在单线程环境下非常好,但是多线程会导致多个实例出现,这个大家都能理解。
最简单的改造方式是添加一个同步锁。
1  package  xylz.study.singleton;
2 
3  public   class  SynchronizedSingleton {
4 
5       private   static  SynchronizedSingleton instance  =   null ;
6 
7       private  SynchronizedSingleton() {
8      }
9 
10       public   static   synchronized  SynchronizedSingleton getInstance() {
11           if  (instance  ==   null ) {
12              instance  =   new  SynchronizedSingleton();
13          }
14           return  instance;
15      }
16  }
17 

显然上面的方法避免了并发的问题,但是由于我们只是在第一次构造对象的时候才需要同步,以后就不再需要同步,所以这里不可避免的有性能开销。于是将锁去掉采用静态的属性来解决同步锁的问题。
1  package  xylz.study.singleton;
2 
3  public   class  StaticSingleton {
4 
5       private   static  StaticSingleton instance  =   new  StaticSingleton();
6 
7       private  StaticSingleton() {
8      }
9 
10       public   static  StaticSingleton getInstance() {
11           return  instance;
12      }
13  }
14 

上面的方法既没有锁又解决了性能问题,看起来已经满足需求了。但是追求“完美”的程序员想延时加载对象,希望在第一次获取的时候才构造对象,于是大家非常聪明的进行改造,也即非常出名的双重检查锁机制出来了。
1  package  xylz.study.singleton;
2 
3  public   class  DoubleLockSingleton {
4 
5       private   static  DoubleLockSingleton instance  =   null ;
6 
7       private  DoubleLockSingleton() {
8      }
9 
10       public   static  DoubleLockSingleton getInstance() {
11           if  (instance  ==   null ) {
12               synchronized  (DoubleLockSingleton. class ) {
13                   if  (instance  ==   null ) {
14                      instance  =   new  DoubleLockSingleton();
15                  }
16              }
17          }
18           return  instance;
19      }
20  }
21 


双重锁机制看起来非常巧妙的避免了上面的问题。但是真的是这样的吗?文章《双重检查锁定及单例模式》中谈到了非常多演变的双重锁机制带来的问题,包括比较难以理解的指令重排序机制等。总之就是双重检查锁机制仍然对导致错误问题而不是性能问题。

一种避免上述问题的解决方案是使用volatile关键字,此关键字保证对一个对象修改后能够立即被其它线程看到,也就是避免了指令重排序和可见性问题。参考文章

指令重排序与happens-before法则

所以上面的写法就变成了下面的例子。

package xylz.study.singleton;

public class DoubleLockSingleton {

    private static volatile DoubleLockSingleton instance = null;

    private DoubleLockSingleton() {
    }

    public static DoubleLockSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleLockSingleton.class) {
                if (instance == null) {
                    instance = new DoubleLockSingleton();
                }
            }
        }
        return instance;
    }
}


于是继续改造,某个牛人利用JVM的特性来解决上述问题,具体哪个牛人我忘记了,但是不是下面文章的作者。
(1)《Java theory and practice: Fixing the Java Memory Model, Part 2
(2)《Initialize-On-Demand Holder Class and Singletons

1  package  xylz.study.singleton;
2 
3  public   class  HolderSingleton {
4 
5       private   static   class  HolderSingletonHolder {
6 
7           static  HolderSingleton instance  =   new  HolderSingleton();
8      }
9 
10       private  HolderSingleton() {
11           // maybe throw an Exception when doing something 
12      }
13 
14       public   static  HolderSingleton getInstance() {
15           return  HolderSingletonHolder.instance;
16      }
17  }
18 




上述代码看起来解决了上面单例模式遇到的所有问题,而且实际上工作的很好,没有什么问题。但是却有一个致命的问题,如果第11行抛出了一个异常,也就是第一次构造函数失败将导致永远无法再次得到构建对象的机会。
使用下面的代码测试下。
1  package  xylz.study.singleton;
2 
3  public   class  HolderSingletonTest {
4 
5       private   static   class  HolderSingletonHolder {
6 
7           static  HolderSingletonTest instance  =   new  HolderSingletonTest();
8      }
9 
10       private   static   boolean  init  =   false ;
11      
12       private  HolderSingletonTest() {
13           // maybe throw an Exception when doing something 
14           if ( ! init) {
15              init = true ;
16               throw   new  RuntimeException( " fail " );
17          }
18      }
19 
20       public   static  HolderSingletonTest getInstance() {
21           return  HolderSingletonHolder.instance;
22      }
23       public   static   void  main(String[] args) {
24           for ( int  i = 0 ;i < 3 ;i ++ ) {
25               try  {
26                  System.out.println(HolderSingletonTest.getInstance());
27              }  catch  (Exception e) {
28                  System.err.println( " one-> " + i);
29                  e.printStackTrace();
30              } catch (ExceptionInInitializerError err) {
31                  System.err.println( " two-> " + i);
32                  err.printStackTrace();
33              } catch (Throwable t) {
34                  System.err.println( " three-> " + i);
35                  t.printStackTrace();
36              }
37          }
38      }
39  }
40 
很不幸将得到以下输出:
1  two -> 0
2  java.lang.ExceptionInInitializerError
3      at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java: 21 )
4      at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java: 26 )
5  Caused by: java.lang.RuntimeException: fail
6      at xylz.study.singleton.HolderSingletonTest. < init > (HolderSingletonTest.java: 16 )
7      at xylz.study.singleton.HolderSingletonTest. < init > (HolderSingletonTest.java: 12 )
8      at xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder. < clinit > (HolderSingletonTest.java: 7 )
9        2  more
10  three -> 1
11  java.lang.NoClassDefFoundError: Could not initialize  class  xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
12      at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java: 21 )
13      at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java: 26 )
14  three -> 2
15  java.lang.NoClassDefFoundError: Could not initialize  class  xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
16      at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java: 21 )
17      at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java: 26 )
18 

很显然我们想着第一次加载失败第二次能够加载成功,非常不幸,JVM一旦加载某个类失败将认为此类的定义有问题,将来不再加载,这样就导致我们没有机会再加载。目前看起来没有办法避免此问题。如果要使用JVM的类加载特性就必须保证类加载一定正确,否则此问题将比并发和性能更严重。如果我们的类需要初始话那么就需要想其它办法避免在构造函数中完成。看起来像是又回到了老地方,难道不是么?

总之,结论是目前没有一个十全十美的单例模式,而大多数情况下我们只需要满足我们的需求就行,没必有特意追求最“完美”解决方案。
原文[ http://www.imxylz.info/p/177.html] 感谢原作者的钻研奉献精神。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值