设计模式之:单例模式

单例设计模式分为两种

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

饿汉式

public class Singleton01 {     //饿汉式,不用考虑线程安全问题
    //私有化构造方法
    private Singleton01(){}

    private static Singleton01 instance;
    //初始化静态变量
    static {
        instance = new Singleton01();
    }
    //获取实例
    public static Singleton01 getInstance() {
        return instance;
    }
}

该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。

instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费

懒汉式

public class Singleton02 {    //懒汉式,需要考虑线程安全问题
    //私有化构造方法
    private Singleton02(){}
    //使用volatile保证可见性和禁止指令重排序
    private static volatile Singleton02 instance= new Singleton02();
    //双重检查锁
    public static Singleton02 getInstance() {
        if(instance==null) //读操作不需要获取锁,直接返回
        {
            synchronized (Singleton02.class)
            {
                if(instance==null)
                {
                    instance = new Singleton02();
                }
            }
        }
        return instance;
    }
}

因为懒汉式采用的是懒加载模式,在调用 getInstance( ) 方法时才会实例化 instance 对象,因此在多线程环境下可能出现线程安全问题。

这里使用了双重检查锁来解决懒汉式的线程安全问题,并使用volatile关键字禁止指令重排序来防止可能出现的空指针问题。

Question 01:为什么不直接使用 synchronized 关键字修饰整个 getInstance( ) 方法

对于 getInstance( ) 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没有必要让每个线程必须持有锁才能调用该方法,这样会严重影响性能

Question 02:volatile 关键字起到了什么作用?

在这里 volatile 关键字是用来保证可见性防止指令重排序

保证可见性:

在多线程环境下,每个线程都有自己的工作内存,线程在操作共享变量时,会先将变量从主内存复制到自己的工作内存中,对变量的操作都在工作内存中进行操作完成后再将变量的值刷新回主内存。如果一个线程修改了共享变量的值,但没有及时刷新回主内存,或者其他线程没有及时从主内存中读取最新的值,就会导致数据不一致的问题。

volatile 关键字可以保证被修饰的变量在被修改后立即刷新到主内存中,同时其他线程在使用该变量时会直接从主内存中读取最新的值,从而保证了变量在多个线程之间的可见性

防止指令重排序导致可能的空指针问题

Java 编译器和处理器为了提高性能,可能会对指令进行重排序,即在不影响单线程程序执行结果的前提下,对代码的执行顺序进行调整。但是,在多线程环境下,指令重排序可能会导致程序出现错误。

volatile 关键字可以禁止编译器和处理器对指令进行重排序,保证代码的执行顺序与编写顺序一致。

在上面的代码里:instance = new Singleton( ); 这行代码在实际执行时会分为三个步骤

  1. 为 Singleton 对象分配内存空间。

  2. 初始化 Singleton 对象。

  3. 将 instance 引用指向分配的内存空间。

没有使用 volatile 关键字的情况下,编译器和处理器可能会对这三个步骤进行重排序,例如先执行步骤 3,再执行步骤 2。在多线程环境下,如果一个线程在执行步骤 3 后,另一个线程判断 instance 不为 null 并直接使用该实例,此时 Singleton 对象却没有被初始化,从而导致返回了一个没有初始化的instance对象。而使用 volatile 关键字修饰 instance 变量后,可以禁止指令重排序,保证这三个步骤按照顺序执行,从而避免了上述问题。

LICENSED UNDER CC BY-NC-SA 4.0
Comment