设计模式之单例模式
1. 概念:
某个类有且仅有一个实例,通过自行实例化向整个系统提供这个实例,这种设计模式被称作单例模式。
2. 特点:
1. 单例类只能有一个实例
2. 单例类必须自己创建自己的唯一实例
3. 单例类必须给所有其他对象提供这一实例
3. 关键点:
1. 构造函数私有化
2. 通过一个静态方法或枚举返回单例对象
3. 确保单例类的对象有且仅有一个,特别是多线程环境下
4. 确保单例类在反序列化时不会重新构建对象
4. 实现方式:
4.1 饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
在单例类初始化时,已经自行实例化,所以饿汉式单例是线程安全的,每次获取单例对象时直接返回该实例,这样节省时间,但由于实例本身是静态的,会一直占据内存空间。
4.2 懒汉式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
只有在使用时才会进行实例化,此种方式实现是线程不安全的,虽然在一定程度上节省了内存空间,但同时导致时间的损耗,而且每次调用getInstance时都进行同步,造成不必要的同步开销。
4.3 Double Check Lock (DLC)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DLC方式实现单例既能够在需要时才会实例化,又能保证线程安全,在实例化之后调用getInstance不再进行同步锁。
另外,上述代码中instance = new Singleton()语句,实际并不是一个原子操作,它可以分为三个步骤:
1. 给Singleton的实例分配内存;
2. 调用Singleton的构造函数,初始化成员变量;
3. 将instance对象指向分配的内存空间(此时instance != null)。
但是由于Java编译器允许处理器乱序执行,在JDK1.5之前的JVM中,上述2和3的执行顺序是无法保证的,也就是说执行顺序有可能是1-2-3或者1-3-2。如果是后者的话,在多线程并发的情况下就有可能出错。在JDK1.5之后可以使用volatile关键字,保证instance对象每次都是从主内存中读取。
4.4 静态内部类单例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
此方式的单例在第一次加载Singleton类时并不会实例化,只有第一次调用getInstance方法时才会实例化。第一次调用getInstance会导致虚拟机加载SingletonHolder类,这种方式既能确保线程安全,也能保证对象唯一,同时也延迟了单例的实例化,这是推荐使用的单例模式实现方式。
4.5 枚举单例
public enum Singleton {
INSTANCE;
}
默认枚举实例的创建都是线程安全的,并且在任何情况下都是只有一个实例。
其他方式的单例,在反序列化后readObject方法会返回一个新实例,枚举单例不存在此问题,其他方式的单例可以通过重写readResolve方法避免:
private Object readResolve() throws ObjectStreamException {
return instance;
}
4.6 使用容器实现单例
public class SingletonManager {
private static HashMap<String, Object> map = new HashMap();
private SingletonManager() {
}
public static void registerService(String key, Object instance) {
if (!map.containsKey(key) {
map.put(key, instance);
}
}
public static Object getService(String key) {
return map.get(key);
}
}
在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象。这种方式可以管理多种类型的单例,并且可以在使用时可以通过统一的借口进行获取,降低了使用成本,也隐藏了具体实现,降低耦合度。
例如Android系统中,使用context获取系统级别的服务,就是用的该方式
注意:
单例对象如果持有Context,很容易引发内存泄漏,最好使用Application Context。
参考资料:
《Android源码设计模式解析与实战》