一、定义

单件模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。

二、适用性

1、当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。

2、当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。(《设计模式:可复用面向对象软件的基础》中这句话暂不能理解)

三、类图

单件模式——Head First-LMLPHP

四、经典实现

public class Singleton {
private static Singleton uniqueInstance;
//其他有用的实例变量 //构造方法是私有的,所以在类外不能new出多个实例
private Singleton(){
//初始化其他实例变量
}
public static Singleton getInstance(){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
//其他的有用方法
}

如果不需要这个实例,它就永远不会产生,这就是“延迟实例化”(lazy instantiaze)。

五、处理多线程

在多线程下,上述经典实现中getInstance方法可能会返回不同的实例(两个线程同时判断出uniqueInstance为null,从而产生两个不同的实例)。针对这种情况,我们有以下三种方法。

1、将getInstance变成同步方法

//通过增加sychronized关键字到getInstance方法中,迫使每个线程在进入这个方法之前,要先等别的线程离开该方法。也即,不会有两个线程可以同时进入这个方法。
public static synchronized Singleton getInstance(){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

缺点:只有第一次执行getInstance时才需要同步,之后每次调用getInstance方法,同步都是一种累赘,从而拖垮性能。

2、“急切”创建实例

public class Singleton {
//在静态初始化器(static initializer)中创建单件,保证线程安全(thread safe)
private static Singleton uniqueInstance=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
//已经有了实例,直接使用它
return uniqueInstance;
}
}

缺点:如果这个对象非常耗费资源,而在程序的执行过程中并没有使用到它,那就造成并资源的浪费。

3、用“双重检查加锁”,在getInstance()中减少使用同步

public class Singleton {
//volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
private Singleton(){
}
public static Singleton getInstance(){
//检查实例,如果不存在,就进入同步区块。只有第一次才彻底执行这里的代码
if(uniqueInstance==null){
synchronized(Singleton.class){
//进入区块后,再检查一次。如果仍是null,才创建实例
if(uniqueInstance==null){
uniqueInstance=new Singleton();
}
}
}
return uniqueInstance;
}
}

缺点:版本兼容问题。在1.4及更早版本的java中,许多JVM对于volatile关键字的实现会导致双重检查加锁的失效。

六、其他

每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单件上,就会产生多个单件并存的怪异现象。解决方法:自行指定类加载器,并指定同一个类加载器。

单例模式会遭到破坏:

  1. 反射:调用setAccessible(true)方法,关掉安全检查,调用私有构造器。预防:修改构造器,让它在被要求创建第二个实例的时候抛出异常。
  2. 序列化和反序列化:对象序列化后再反序列化时, 会有一个新的对象被克隆出来。预防:加入readResolve()回调方法,返回指定的对象。
04-18 03:27