设计模式-单例模式
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
有时候,我们要求一个类仅有一个实例,这种场景下单例模式就可以派上用场。先看下单例模式的类图。
UML类图
单例模式的关键是构造函数定义为私有的,防止在外部实例化该类对象,并提供一个获取单例对象的公共方法。
代码实现
单例模式实现可分为单线程和多线程的,也即是否线程安全的。
单线程的单例
/**
* <p>文件描述: 单线程的单例</p>
*
* @Author luanmousheng
* @Date 17/6/25 下午2:48
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
多线程的单例
多线程下创建单例不是线程安全的,有两种方式可以解决这个问题,DCL加锁机制和静态初始化机制。加锁保证了同时只有一个线程去创建单例,静态初始化是借助了类加载机制。
双重检查锁定(Double Check Locking, DCL)
/**
* <p>文件描述: 双重检查锁定机制:DCL</p>
*
* @Author luanmousheng
* @Date 17/6/25 下午2:59
*/
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查锁定的第一次检查,是为了判断实例是否已经存在,只有当实例不存在才会去创建,否则每次调用getInstance方法都要去做同步锁定,效率太低。
在第一次检查和第二次检查之间的同步块,是为了保证同时只有一个线程创建实例对象。
那为何要有第二次检查呢?假如一开始单例实例不存在,A和B线程都通过了第一次检查,假设线程A竞争到了同步锁,进入到了同步块内,直接创建实例。当线程A离开同步块后,线程B拿到同步锁并进入到同步块,也会创建实例,那就创建了两次实例。因此第二次检查是必要的。
静态变量singleton为何被声明为volatile的?singleton = new Singleton()
不是原子的操作,将singleton声明为volatile,禁止了指令重排序,防止其他线程拿到了还未初始化完成的对象引用(CPU可能先给singleton赋值为对象的地址,然后去初始化对象,此时其他线程就可能拿到未初始化完成的对象引用)。
静态初始化机制
静态初始化机制包括类静态变量的初始化和静态块的初始化
静态变量初始化
/**
* <p>文件描述: 静态变量初始化</p>
*
* @Author luanmousheng
* @Date 17/6/25 下午3:33
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
这种方法利用了类加载机制,类加载的时候就创建了对象。
静态块初始化
/**
* <p>文件描述: 静态内部类机制</p>
*
* @Author luanmousheng
* @Date 17/6/25 下午3:40
*/
public class Singleton {
/**
* 静态内部类
*/
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
/**
* <p>功能描述: 只有真正调用该方法时才会去初始化instance</p>
*
* @Author luanmousheng
* @Date 17/6/25 下午3:42
*/
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
这种方式最大的优点是延迟初始化,只有在需要的时候才会去创建实例。
参考:
- 大话设计模式. 程杰著