单例模式的各种写法评测

单例模式(Singleton):

  单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
  1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
  2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
  3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。
  显然单例模式的要点有三个:
   一是某各类只能有一个实例;
  二是它必须自行创建这个事例;
  三是它必须自行向整个系统提供这个实例。
  一些资源管理器常常设计成单例模式,在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler, 以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
  实现单例的三个条件:
  1.静态的私有化属性;2.私有构造器;3.提供公共的访问该类实例的方法.
       下面列举一些常见的单例模式:
  (一)普通饿汉式
  
1 public class Singleton {   
2     private static Singleton instance = new Singleton();   
3   
4     public static Singleton getInstance() {   
5           return instance;   
6     }   
7 }  

优点:

  1.线程安全 
  2.在类加载的同时已经创建好一个静态对象,调用时反应速度快

缺点:

  资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化

(二)普通的懒汉式:

 

 1 public class Singleton {  
 2   
 3     /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
 4     private static Singleton instance = null;  
 5   
 6     /* 私有构造方法,防止被实例化 */  
 7     private Singleton() {  
 8     }  
 9   
10     /* 静态工程方法,创建实例 */  
11     public static Singleton getInstance() {  
12         if (instance == null) {  
13             instance = new Singleton();  
14         }  
15         return instance;  
16     }  
17   
18     /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
19     public Object readResolve() {  
20         return instance;  
21     }  
22 }  

  这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,instance对象被多条语句所操作(判断为空和创建实例);所以该类需要优化,可以加同步来解决,而加同步的方式使用同步函数和同步代码块都行,但稍微有些低效,用双重判断的方式能解决效率问题:
(三)优化后的懒汉式:

 1 public static Singleton getInstance() {  
 2         if (instance == null) {  
 3             synchronized (Singleton.class) {  
 4                 if (instance == null) {  
 5                     instance = new Singleton();  
 6                 }  
 7             }  
 8         }  
 9         return instance;  
10     }  
  优点:资源利用率高,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。可以执行该类其他静态方法
  缺点:第一次加载时反应不快,由于java内存模型一些原因偶尔失败!
  但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
  a)A、B线程同时进入了第一个if判断
  b)A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  c)由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  d)B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  e)此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。
但是.jdk1.5之后上面的都是废话...因为JDK1.5已经没有双重检查锁定的问题了

(四)静态内部类:

 1 public class Singleton {  
 2   
 3     /* 私有构造方法,防止被实例化 */  
 4     private Singleton() {  
 5     }  
 6   
 7     /* 此处使用一个内部类来维护单例 */  
 8     private static class SingletonFactory {  
 9         private static Singleton instance = new Singleton();  
10     }  
11   
12     /* 获取实例 */  
13     public static Singleton getInstance() {  
14         return SingletonFactory.instance;  
15     }  
16   
17     /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
18     public Object readResolve() {  
19         return getInstance();  
20     }  
21 }

  这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式就显得很合理。注意:如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。而上述代码17行就是解决这个问题的方法.

  实际情况中,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。但是,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

(五)枚举

1 1.public enum Singleton {   
2 2.    INSTANCE;   
3 3.    public void whateverMethod() {   
4 4.    }   
5 5.} 

  这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

 

总结:

  1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。
  2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)
  3、一般采用饿汉式,若对资源十分在意可以采用静态内部类或者采用懒汉式的双重检测版(JDK1.5版本后);

 

 

 

posted on 2013-07-04 22:26  NFC  阅读(1431)  评论(7编辑  收藏  举报

导航