设计模式之单例模式

85 阅读2分钟

为什么要用单例模式 (单例模式的意义)

确保类只有一个实例

单例模式的构造函数为什么是private

防止外部实例化、防止继承和子类化

单例模式是怎么保存唯一实例的?

内部设置一个静态成员变量来保证唯一的实例

饿汉式

优点:线程安全,没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

懒汉式

线程不安全的实现方式

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

在多线程下可能会产生多个实例。不可用。

线程安全的实现方式

优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

引入了synchronized关键字加锁,会引入性能开销。

双重校验锁DCL 线程安全

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

为什么要使用volatile关键字?

禁止指令重排序。
singleton = new Singleton();
在对象创建的过程中,不是只有一条指令,而是三条指令。
第一条指令是在内存中开辟空间,然后对对象进行半初始化。
第二条指令是对对象进行初始化。
第三条指令是对象指向内存地址。

问题的出现是因为在创建对象的时候,第二条指令和第三条指令会进行重排序。假设此时线程A通过第二次检查,在创建对象的过程中,第二条指令和第三条指令出现重排序,此时instance指向的是一个半初始化的对象。如果在这个时候线程B进来了,在执行第一次检查的时候发现instance并非为空,这时就直接返回了半初始化的instance,从而会出现问题。


使用volatile时为了确保在多线程环境下,所有线程都看到一个一致的状态。

静态内部类

使用静态内部类来实现懒汉式单例模式,保证线程安全和性能。
这种方式能达到双检锁方式一样的功效,但实现更简单。

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚举 jdk1.5起 线程安全

不能使用反射来获取构造函数进行构造
无法满足懒加载

public enum PersonSingleton {
    INSTANCE;
}