设计模式之单例模式

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源码设计模式解析与实战》

posted @ 2017-03-23 23:20  唐家柒少  阅读(125)  评论(0编辑  收藏  举报