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