Java设计模式——单例模式

概述

单例模式:确保某个类只有一个实例,并自行实例化向整个系统提供这个实例

实现单例模式的关键点:

  • 构造函数不对外开放,一般为private
  • 通过一个静态方法或者枚举返回单例类对象
  • 确保单例类的对象有且只有一个,尤其在多线程环境下
  • 确保单例类对象在反序列化时不会重新构建对象

通过将单例类的构造函数私有化,使得代码不能通过new的形式手动构造类对象,单例类会暴露一个公有静态方法来获取唯一对象

饿汉模式

public class Singleton {
    private static final Singleton instance = new Singleton();

    // 构造函数私有化
    private Singleton(){}

    // 公有静态函数,对外暴露获取单例对象的接口
    public static Singleton getInstance() {
        return instance;
    }
}

Singleton对象是静态对象,并且在声明的时候就已经初始化,保证了对象的唯一性

懒汉模式

public class Singleton {
    private static Singleton instance;

    // 构造函数私有化
    private Singleton(){}

    // 公有静态函数,对外暴露获取单例对象的接口
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

getInstance方法添加了synchronized关键字,这就是所说的在多线程的情况下保证单例对象唯一性的手段。

优点:只有在使用时才会实例化,一定程度上节约了资源
缺点:第一次加载时需要及时实例化,反应稍慢,每次调用都进行同步,造成不必要的同步开销

Double Check Lock(DCL)模式

DCL方式的优点是既能够在需要时才初始化实例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁

public class Singleton {
    private volatile static Singleton instance = null;
    // 构造函数私有化
    private Singleton(){}

    // 公有静态函数,对外暴露获取单例对象的接口
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

DCL模式是使用最多的单例实现方式。优点:资源利用率高,效率高。缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败,在高并发情况下也有一定缺陷。

DCL失效问题: 假设线程A执行到instance = new Singleton()语句,这里看起来是一句代码,但实际上他并不是一个原子操作,这句代码最终会编译成多条汇编指令,大致如下:
1. 给Singleton的实例分配内存
2. 调用Singleton()的构造函数,初始化成员字段
3. 将instance对象指向分配的内存空间(此时instance就不是null了)

由于Java编译器允许处理器乱序执行,在JDK1.5之前Java内存模型中Cache,寄存器到主内存回写顺序的规定,上面2,3的顺序无法保证就可能出现132的错误顺序。

静态内部类模式

DCL虽然一定程度上解决了资源消耗、多余的同步、线程安全等问题,但在某些情况下还是会失效。在《Java并发编程实践》一书中建议使用如下代码替代:

public class Singleton {

    // 构造函数私有化
    private Singleton(){}

    // 公有静态函数,对外暴露获取单例对象的接口
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
}

枚举模式

枚举单例最大的优点就是写法简单,并且在任何情况下它都是一个单例。(以上的方式在反序列化的情况下会出现重新创建对象)


public class Singleton {

    // 构造函数私有化
    private Singleton(){}

    // 公有静态函数,对外暴露获取单例对象的接口
    public static Singleton getInstance() {
        return SingletonEnum.INSTANCE.getInstance();
    }

    private enum SingletonEnum {
        INSTANCE;

        private Singleton singleton;

        // JVM会保证此方法只调用一次
        SingletonEnum() {
            singleton = new Singleton();
        }

        public Singleton getInstance() {
            return singleton;
        }
    }
}

以上其他模式要杜绝单例对象在被反序列化时重新生成对象,需要加入如下方法:

private Object readResolve() throws ObjectStreamException {
        return instance;
    }

《Effective Java》一书中的话:单元素的枚举类型已经成为实现Singleton的最佳方法。

容器模式

public class SingletonManager {
    private static Map objMap = new HashMap<>();

    private SingletonManager() {}
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }
}

在程序的初始,将多种单例类型注入到一个统一的管理类中,使用时根据key获取对象对应类型的对象。

可参考Android中ContextImpl类中的SYSTEM_SERVICE_MAP

总结

不管以哪种形式实现单例,核心原理都是将构造函数私有化,并通过静态方法获取唯一的实例,在获取过程中必须保证线程安全、防止反序列化等问题,选择哪种实现方式取决于项目本身。

优点:

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显了

  • 由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决

  • 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作

  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理

缺点:

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现

  • 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context

你可能感兴趣的:(Android基础,JAVA,设计模式,JAVA,设计模式)