设计模式--单例(Singleton Pattern)

231 阅读3分钟

定义

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

使用场景

避免产生多个对象消耗过多的资源,或者某种类型的对象只有一个。

1、饿汉模式

public static class Singleton {
    private static final instance Singleton = new Singleton();
    
    private Singleton() { // 私有构造函数
    }
    
    public static Singleton getInstance() {
        return instance;
    }
}

instance是静态成员变量,在声明时就被实例化。

2、懒汉模式

public static class Singleton {
    private static final instance Singleton = null;
    
    private Singleton() { // 私有构造函数
    }
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton()
        }
        return instance;
    }
}

instance只是在初次调用时初始化,synchronized,每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源,这也是懒汉模式最大的问题。

3、Double Check Lock(DCL)

public static class Singleton {
    private static final instance Singleton = null;
    
    private Singleton() { // 私有构造函数
    }
    
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton()
                }
            }
        }
        return instance;
    }
}

DCL有两次判空,第一次判空,是为了避免不必要的同步;第二次判空,是为了初次初始化时,只初始化一次。

instance = new Singletone(); 这行代码最终会编译成多条汇编指令,它大致做了三件事:

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

DCL失效情况:
由于Java编译器允许处理器乱序执行,以及JDK1.5之前JMM(Java Memory Model)中Cache、寄存器到内存回写顺序的规定,上面第二和第三的顺序是无法保证的。也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例,这样就可能出错了。我们以A、B两个线程为例:

  1. A、B线程同时进入了第一个if判断
  2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

在JDK1.5之后,SUN公司调整了JVM,增加了volatile关键字,只需将instance的定义改成private volatile static Singleton instance = null就可以保存instance对象每次都是从主内存总读取。

4.静态内部类单例模式

public static class Singleton {
    private Singleton() { // 私有构造函数
    }
    
    private static class SingletonHolder {
        private static final instance Singleton = new Singleton();
    }
    
    public static synchronized Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

当第一次加载Singleton类时,并不会初始化,只有当第一次调用getInstance()方法,会导致虚拟机加载SingletonHolder类,此时才是初始化。利用的Java类加载机制

不同的类装载器,可能会导致产生多个单例对象:

public static Class getClass(String className) throws ClassNotFoundException {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
        cl = Singleton.class.getClassLoader();
    }
    return cl.loadClass(className);
}

如果Singleton实现了Serializable接口,反序列化时可能产生多个实例对象:

private Object readResolve() { // 重写readResolve方法
    return instance;
}