Java单例模式

作为对象的创建模式[GOF95]单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。由定义可以总结出单例模式的要点有三个:一是单例类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

虽然从类图上看,单例模式是最简单的设计模式之一,但是真正正确地使用单例模式却不是那么简单的事。


首先看一个经典的单例实现。

public class Singleton {
    private static Singleton uniqueInstance = null;
 
    private Singleton() {
       // Exists only to defeat instantiation.
    }
 
    public static Singleton getInstance() {
       if (uniqueInstance == null) {
           uniqueInstance = new Singleton();
       }
       return uniqueInstance;
    }
    // Other methods...
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实例。

有很多种方法可以实现线程安全的单例模式,下面逐一介绍:

1. 一步到位的饿汉单例类

饿汉式单例类是在Java 语言里实现得最为简便的单例类。在类被加载时,就会将自己实例化。

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
 
    private Singleton() {
       // Exists only to defeat instantiation.
    }
 
    public static Singleton getInstance() {
       return uniqueInstance;
    }
    // other methods...
}

2. 改造经典模式

首先是最简单最直接的改造。

public class Singleton {
    private static Singleton uniqueInstance = null;
 
    private Singleton() {
       // Exists only to defeat instantiation.   
    }
 
    public synchronized static Singleton getInstance() {
       if (uniqueInstance == null) {
           uniqueInstance = new Singleton();
       }
       return uniqueInstance;
    }
    //Other methods...
}

通过synchronized关键字,同步了不同线程对getInstance()的访问。这就是所谓的懒汉模式。与饿汉式单例类不同的是,懒汉式单例类在第一次被引用时将自己实例化。这种简单实现的问题在于,每次访问getInstance()都需要同步操作,而事实上同步只在第一次访问时有意义。为了避免不必要的同步操作,在JDK1.5以后可以使用一种双重检查加锁的方法。

public class Singleton {
    // volatile is very important for uniqueInstance consistency.
    private volatile static Singleton uniqueInstance = null;
 
    private Singleton() {
       // Exists only to defeat instantiation.
    }
 
    public static Singleton getInstance() {
       // first check no need to synchronize.
       if (uniqueInstance == null) {
           // second check need to synchronize, but only run limit times.
           synchronized (Singleton.class) {
              if (uniqueInstance == null) {
                  uniqueInstance = new Singleton();
              }
           }
       }
       return uniqueInstance;
    }
    // Other methods...
}

volatile确保uniqueInstance被初始化为单例后的改变对所有线程可见,多线程能够正确处理uniqueInstance变量。getInstance()中包含两次判空操作,第一次判空每次访问都会执行,而第二次判空只在初始访问存在大量并发的情况下出现。通过两次判空避免了不必要的线程同步。之所以限制必须在JDK1.5后使用是因为,之前的Java存储模型不能保证volatile语义的完全正确实现。为了突破这种限制《Effective Java》中给出了一种精妙的解决方法,充分利用了Java虚拟机的特性。

public class Singleton {
    // an inner class holder the uniqueInstance.
    private static class SingletonHolder {
       static final Singleton uniqueInstance = new Singleton();
    }
 
    private Singleton() {
       // Exists only to defeat instantiation.
    }
 
    public static Singleton getInstance() {
       return SingletonHolder.uniqueInstance;
    }
    // Other methods...
}

3. 登记式单例类

登记式单例类是GoF 为了克服饿汉式单例类及懒汉式单例类均不可继承的缺点而设计的。

public class RegSingleton {
    static private HashMap m_registry = new HashMap();
    static {
       RegSingleton x = new RegSingleton();
       m_registry.put(x.getClass().getName(), x);
    }
 
    protected RegSingleton() {
    }
 
    public static RegSingleton getInstance(String name) {
       if (name == null) {
           name = "com.javapatterns.singleton.demos.RegSingleton";
       }
       if (m_registry.get(name) == null) {
           try {
              m_registry.put(name, Class.forName(name).newInstance());
           } catch (ClassNotFoundException cnf) {
              System.out.println("Couldn't find class " + name);
           } catch (InstantiationException ie) {
              System.out.println("Couldn't instantiate an object of type "+ name);
           } catch (IllegalAccessException ia) {
              System.out.println("Couldn't access class " + name);
           }
       }
       return (RegSingleton) (m_registry.get(name));
}
}
// sub-class implements RegSingleton.
public class RegSingletonChild extends RegSingleton {
    public RegSingletonChild() {
    }
 
    static public RegSingletonChild getInstance() {
       return (RegSingletonChild) RegSingleton
              .getInstance("com.javapatterns.singleton.demos.RegSingletonChild");
    }
 
    public String about() {
       return "Hello, I am RegSingletonChild.";
    }
}

GoF 原始的例子中,并没有getInstance() 方法,这样得到子类必须调用的getInstance(String name)方法并传入子类的名字,因此很不方便。加入getInstance() 方法的好处是RegSingletonChild 可以通过这个方法,返还自已的实例。而这样做的缺点是,由于数据类型不同,无法在RegSingleton 提供这样一个方法。由于子类必须允许父类以构造子调用产生实例,因此,它的构造子必须是公开的。这样一来,就等于允许了以这样方式产生实例而不在父类的登记中。这是登记式单例类的一个缺点。GoF 曾指出,由于父类的实例必须存在才可能有子类的实例,这在有些情况下是一个浪费。这是登记式单例类的另一个缺点。

转载自http://www.cnblogs.com/zhuowei/archive/2009/01/04/1368611.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值