单例模式又叫做单态模式或者单件模式。在 GOF 书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式中的“单例”通常用来代表那些本质上有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。
单例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的变对象的个数。一个类的对象的产生是由类构造函数来完成,如果想限制对象的产生,一个办法就是将构造函数变为私有的(至少是受保护的) ,使外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必须提供一个自己的对以及访问这个对象的静态方法。 其实单例模式在实现上是非常简单的——只有一角色,而客户则通过调用类方法来得到类的对象。
单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象,多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。
实现:
饿汉式:
- public class Singleton {
- //在自己内部定义自己一个实例
- //注意这是 private 只供内部调用
- private static Singleton instance = new Singleton();
- //如上面所述,将构造函数设置为私有
- private Singleton(){
- }
- //静态工厂方法,提供了一个供外部访问得到对象的静态方法
- public static Singleton getInstance() {
- return instance;
- }
- }
懒汉式:
- public class Singleton {
- //和上面有什么不同?
- private static Singleton instance = null;
- //设置为私有的构造函数
- private Singleton(){
- }
- //静态工厂方法
- public static synchronized Singleton getInstance() {
- //这个方法比上面有所改进
- if (instance==null)
- instance=new Singleton();
- return instance;
- }
- }
先让我们来比较一下这两种实现方式。 首先他们的构造函数都是私有的,彻底断开了使用构造函数来得到类的实例的通道,但是这样也使得类失去了多态性(大概这就是为什么有人将这种模式称作单态模式)。 在第二种方式中,对静态工厂方法进行了同步处理,原因很明显——为了防止多线程环境中产生多个实例;而在第一种方式中则不存在这种情况。 在第二种方式中将类对自己的实例化延迟到第一次被引用的时候。而在第一种方式中则是在类被加载的时候实例化,这样多次加载会照成多次实例化。但是第二种方式由于使用了同步处理,在反应速度上要比第一种慢一些。 在 《java 与模式》书中提到,就 java 语言来说,第一种方式更符合 java 语言本身的特点。
- import java.util.HashMap;
- public class Singleton
- {
- //用来存放对应关系
- private static HashMap sinRegistry = new HashMap();
- static private Singleton s = new Singleton();
- //受保护的构造函数
- protected Singleton()
- {}
- public static Singleton getInstance(String name) {
- if(name == null)
- name = "Singleton";
- if(sinRegistry.get(name)==null) {
- try{
- sinRegistry.put(name , Class.forName(name).newInstance());
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- return (Singleton)(sinRegistry.get(name));
- }
- public void test()
- {
- System.out.println("getclasssuccess!");
- }
- }
- public class SingletonChild1 extends Singleton
- {
- public SingletonChild1(){}
- static public SingletonChild1 getInstance()
- {
- return (SingletonChild1)Singleton.getInstance("SingletonChild1");
- }
- public void test()
- {
- System.out.println("getclasssuccess111!");
以上两种实现方式均失去了多态性,不允许被继承。还有另外一种灵活点的实现,将构 造函数设置为受保护的,这样允许被继承产生子类。这种方式在具体实现上又有所不同,可 以将父类中获得对象的静态方法放到子类中再实现;也可以在父类的静态方法中进行条件判 断来决定获得哪一个对象;在 GOF 中认为最好的一种方式是维护一张存有对象和对应名称 的注册表(可以使用 HashMap 来实现)。下面的实现参考《java 与模式》采用带有注册表 的方式。
第三种实现方式:
- public class Singleton
- {
- private static class SingletonHolder
- {
- static Singleton instance = new Singleton();
- }
- public static Singleton getInstance()
- {
- return SingletonHolder.instance;
- }
- }
多个类加载器 当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间amespace)来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。 也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少。在很多 J2EE服务器上允许存在多个 servlet 引擎,而每个引擎是采用不同的类加载器的;览器中 applet 小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,等。 这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一情况下不要使用存在状态的单例模式。 错误的同步处理 在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。
串行化(可序列化) 为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入 readResolve 方法
抛开单例模式,使用下面一种简单的方式也能得到单例,而且如果你确信此类永远是单 例的,使用下面这种方式也许更好一些。 public static final Singleton INSTANCE = new Singleton(); 而使用单例模式提供的方式,这可以在不改变 API的情况下,改变我们对单例类的具体 要求。