单例模式

244 阅读3分钟

概念

保证java虚拟机范围内只有一个实例,并提供该实例的一个全局访问点。关键是一下几点:

  • 私有构造函数
  • 静态方法或枚举返回单例类对象
  • 确保在多线程环境下也只有一个对象
  • 确保在反序列化时不会重新创建对象。

优点

  • 减少内存开支(特别是一个对象须频繁创建销毁时)和性能开销
  • 避免多重占用资源(比如将写操作封装为一个单例可以避免对同一个资源文件的同时写)
  • 优化共享资源的访问(单例模式有全局访问点)

缺点

  • 没有接口,扩展困难(只能修改代码)
  • 单例模式若持有Context容易引发内存泄漏(所以尽量传的是Application Context)

实现方式

  • 饿汉式

线程安全,类加载时创建好了,以空间换时间

private static Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
    return instance;
}
  • 懒汉式

延迟加载和缓存,以时间换空间。有synchronized关键字保证线程安全。不过每次取得实例都要进行同步造成了额外开销。

private static Singleton instance = null;

private Singleton(){}

public static synchronized Singleton getInstance(){
    if(instance == null){
        instance = new Singleton();
    }
    return instance;
}
  • Double CheckLock

资源利用率高,只在需要时才初始化对象,第一次读取才进行同步,使用最多的单例模式,但在高并发情况下也有一定缺陷。

private volatile static Singleton instance = null;//保证从主内存读取instance对象,保证获取到正确的对象

private Singleton(){}

public static Singleton getInstance(){
    if(instance == null){ //第一次:用来判断只在第一次时执行下面的代码,后面有了直接返回,避免创建不必要同步,
        synchronized(Singleton.class){
            if(instance == null){ //第二次:进入同步块后检查在null的情况下创建实例。避免由于CPU时间片切换创建多个对象
                instance = new Singleton();
            }
        }      
    }
    return instance;
}

注意instance = new Singleton()并非原子操作,它分为以下三步

1 给 Singleton的实例分配内存 2 调用Singleton的构造函数,初始化成员字段 3 将instance对象指向分配的内存空间(此时instance才不为null)

由于JVM存在指令重排序,实际执行顺序可以123或132,若是132会导致多线程下DCL失效,所以需要加上volatile保证从主内存读取,这样所有线程获取到的对象都是已经初始化好的

  • 静态内部类

利用了java外部类访问内部类私有成员的机制,只在第一次调用getInstance()时才会导致instance初始化(虚拟机装载SingletonHolder),线程安全。值得推荐使用。

Java语言规范说了enclosing class可以访问inner class的private/protected成员,inner class也可以访问enclosing class的private/protected成员。编译器实现的时候是这样的:enclosing class和inner class不再是嵌套结构,而是变为一个包中的两个类,然后,对于private变量的访问,编译器会生成一个accessor函数.

内部类的分类

  • 类级别(static):相当于外部类的static成员,其对象与外部类对象实例不存在依赖性。可直接创建。只在其第一次使用时才装载类。
  • 对象级别:必须绑定在外部类对象实例上
private Singleton(){}

public static Singletonm getInstance(){
    return SingletonHolder.instance;
}

private static class SingletonHolder{
    private static final Singleton instance = new Singleton();
}
  • 枚举单例

写法简单,在任何情况下(包括多线程和反序列化)都是一个单例

public enum SingletonEnum{
    INSTANCE;
    public void fun(){
        //do something
    }
}

上面所有单例的实现方式,若要防止反序列化时重新生成对象,则都需要加入以下方法

private Object readResolve() throws ObjectStreamException() {
    return instance;
}
  • 使用容器

将多种单例类型注入到一个统一的管理类(Singleton)中,可以管理多种类型的单例。降低耦合度

public class Singleton{

    private static Map<String, Object> map = new HashMap<String, Object>();

    private Singleton(){}

    public static void resgisterService(String key, Object instance){
        if(!map.containsKey(key)){
            map.put(key, instance);
        }
    }

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

}