单例模式又叫做单态模式或者单件模式。在 GOF 书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式中的“单例”通常用来代表那些本质上有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。

    单例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的变对象的个数。一个类的对象的产生是由类构造函数来完成,如果想限制对象的产生,一个办法就是将构造函数变为私有的(至少是受保护的) ,使外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必须提供一个自己的对以及访问这个对象的静态方法。 其实单例模式在实现上是非常简单的——只有一角色,而客户则通过调用类方法来得到类的对象。 

    单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象,多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。
    

实现:

饿汉式:

 
  
  1. public class Singleton {  
  2. //在自己内部定义自己一个实例  
  3. //注意这是 private 只供内部调用  
  4.    private static Singleton instance = new Singleton();  
  5.     //如上面所述,将构造函数设置为私有  
  6.    private Singleton(){  
  7.    }    
  8.    //静态工厂方法,提供了一个供外部访问得到对象的静态方法   
  9.    public static Singleton getInstance() {  
  10.      return instance;       
  11.    }   
  12. }  

懒汉式:

  1. public class Singleton {   
  2.  //和上面有什么不同?  
  3. private static Singleton instance = null;  
  4. //设置为私有的构造函数  
  5. private Singleton(){  
  6. }    
  7. //静态工厂方法  
  8. public static synchronized Singleton getInstance() {  
  9. //这个方法比上面有所改进  
  10.     if (instance==null)  
  11.   instance=new Singleton();  
  12.     return instance;       
  13. }   
  14. }   

先让我们来比较一下这两种实现方式。
首先他们的构造函数都是私有的,彻底断开了使用构造函数来得到类的实例的通道,但
是这样也使得类失去了多态性(大概这就是为什么有人将这种模式称作单态模式)。
在第二种方式中,对静态工厂方法进行了同步处理,原因很明显——为了防止多线程环
境中产生多个实例;而在第一种方式中则不存在这种情况。
在第二种方式中将类对自己的实例化延迟到第一次被引用的时候。而在第一种方式中则
是在类被加载的时候实例化,这样多次加载会照成多次实例化。但是第二种方式由于使用了
同步处理,在反应速度上要比第一种慢一些。
在 《java 与模式》书中提到,就 java 语言来说,第一种方式更符合 java 语言本身的
特点。

 
  
  1. import java.util.HashMap;  
  2.   
  3. public class Singleton  
  4. {  
  5.  //用来存放对应关系  
  6.   private static HashMap sinRegistry = new HashMap();  
  7.   static private Singleton s = new Singleton();  
  8.   //受保护的构造函数  
  9.   protected Singleton()  
  10.   {}  
  11.   public static Singleton getInstance(String name)  {  
  12.    if(name == null)  
  13.      name = "Singleton";  
  14.   if(sinRegistry.get(name)==null)   {  
  15.     try{  
  16.      sinRegistry.put(name , Class.forName(name).newInstance());  
  17.      }catch(Exception e)  {  
  18.         e.printStackTrace();   
  19.      }   
  20.    }  
  21.   return (Singleton)(sinRegistry.get(name));   
  22.  }  
  23.  public void test()  
  24.  {  
  25.   System.out.println("getclasssuccess!");   
  26.  }  
  27. }  
  28.   
  29. public class SingletonChild1 extends Singleton  
  30. {  
  31.  public SingletonChild1(){}  
  32.  static  public SingletonChild1 getInstance()  
  33.  {  
  34.   return (SingletonChild1)Singleton.getInstance("SingletonChild1");   
  35.  }  
  36.  public void test()  
  37.  {  
  38.   System.out.println("getclasssuccess111!"); 

以上两种实现方式均失去了多态性,不允许被继承。还有另外一种灵活点的实现,将构
造函数设置为受保护的,这样允许被继承产生子类。这种方式在具体实现上又有所不同,可
以将父类中获得对象的静态方法放到子类中再实现;也可以在父类的静态方法中进行条件判
断来决定获得哪一个对象;在 GOF 中认为最好的一种方式是维护一张存有对象和对应名称
的注册表(可以使用 HashMap 来实现)。下面的实现参考《java 与模式》采用带有注册表
的方式。

第三种实现方式:


 
  
  1. public class Singleton   
  2. {   
  3.     private static class SingletonHolder   
  4.     {   
  5.         static Singleton instance = new Singleton();   
  6.     }     
  7.     public static Singleton getInstance()   
  8.     {   
  9.         return SingletonHolder.instance;   
  10.     }   
  11. }   



多个类加载器
当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字
都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间
amespace)来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。
也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少
。在很多 J2EE服务器上允许存在多个 servlet 引擎,而每个引擎是采用不同的类加载器的;
览器中 applet 小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,
等。
这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一
情况下不要使用存在状态的单例模式。
错误的同步处理
在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能
达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定
对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。

串行化(可序列化)
为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不
够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是
一个对原有对象的引用。为了防止这种情况,可以在单例类中加入 readResolve 方法

抛开单例模式,使用下面一种简单的方式也能得到单例,而且如果你确信此类永远是单
例的,使用下面这种方式也许更好一些。
public static final Singleton INSTANCE = new Singleton();
而使用单例模式提供的方式,这可以在不改变 API的情况下,改变我们对单例类的具体
要求。