单例设计模式

余生颓废 提交于 2020-01-18 02:28:38

什么是单例设计模式?

 单例模式,是一种常见的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式的方法创建的类在当前进程中只有一个实例。

 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

单例模式有以下特点:

  1. 单例类只能有一个实例
  2. 单例类必须自己创建自己的唯一实例
  3. 单例类必须给所有的其他对象提供这一实例

具体实现

需要:

  1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象
  2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型
  3. 定义一个静态方法返回这个唯一对象
实现一:立即加载/“饿汉模式”
/**
 * 饿汉式:在类初始化的时候,已经创建自己的实例
 */
public class Singleton {
    //1,私有化构造方法
    private Singleton(){}

    //2,创建自己的单例对象
    private final static Singleton singleton = new Singleton();

    //3,提供获取当前类对象的方法
    public static Singleton getInstance(){
        return singleton;
    }
}

优点: 线程安全,实现简单
缺点: 类一加载就初始化了对象,就算没有使用也一直占用着内存,会让内存开销变大

实现二:延迟加载/“饿汉模式”
/**
 *
 * 懒汉式:方法被调用的时候才实例化
 */
public class Singleton {

    //1,私有化构造方法
    private Singleton(){}

    //2,静态私有的属性
    private static Singleton singleton = null;

    //3,提供获取当前类对象的方法
    public static Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

优点: 实现简单,当getInstance方法第一次被调用时菜初始化instance变量,并分配内存,因此在某些特定条件下节约了内存。
缺点: 线程不安安全

实现三:线程安全的“懒汉式”
public class Singleton {

    // 构造方法私有化
    private Singleton() {}

    // 将自身实例化对象设置为一个属性,并用static修饰
    private static Singleton instance;
    
    // 静态方法返回该实例,加synchronized关键字实现同步
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优点: 在多线程情况下,保证了“懒汉模式”的线程安全
缺点: synchronized方法通常效率低,而且无论instance对象创建与否,线程都进行排队等候,效率低下。

实现四: DCL双检锁机制
public class Singleton {

    // 将自身实例化对象设置为一个属性,并用static修饰
    private static Singleton instance;
    
    // 构造方法私有化
    private Singleton() {}
    
    // 静态方法返回该实例
    public static Singleton getInstance() {
        // 第一次检查instance是否被实例化出来,如果没有进入if块
        if(instance == null) {
            synchronized (Singleton.class) {
                // 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

优点: 内存占用率高,效率高,线程安全
缺点: 存在指令重排问题

实现四中指令重排问题:
    Singleton s = new Singleton();
    执行上述语句时实际做了三件事:
        1. 开辟内存空间
        2. 实例化对象
        3. 将内存地址返回
    但在编译执行的时候会有指令重排问题,,编译器优化时为了减少内存的开销提高性能,执行顺序会变成:
        1=>3=>2,即一开辟内存就将内存地址返回了,然后才实例化对象。

所以在实现四的单例模式中,可能出现以下情况:
    如下图线程A拿到线程锁执行new语句时,开辟了内存空间并返回内存地址,
    但是并没有实例化对象,此时线程B执行了外层判断对象s不为空,
    就将其返回,但此时对象并没有实例化,就会出现问题。

image.png

实现五:加关键字volatile
public class Singleton {

    // 保证了并发编程中的有序性
    private volatile static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        // 第一次检查instance是否被实例化出来,如果没有进入if块
        if(instance == null) {
            synchronized (Singleton.class) {
                // 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注意:
    需要加volatile关键字的原因是,在并发情况下,如果没有这个关键字,
    在第5行会出现问题。因为第五行代码“instance = new Singleton()”
    并不是原子性操作,在JVM中被分为如下三个阶段执行:
        1. 为instance分配内存
        2. 初始化instance
        3. 将instance变量指向分配的内存空间
        
            由于JVM可能存在重排序,可能会执行第3步然后再执行第2步。也就是说可能
        会出现instance变量还没有初始化完成,其他线程就已经判断了该变量不为null,
        结果返回了 一个没有初始化的半成品。而加上volatile关键字,可以保证instance
        变量的操作不会被JVM重排。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!