三、单例模式:双重检查锁和静态内部类的两种写法

一、双重检查锁

先看看常见错误写法:

public class Singleton {
    private static Singleton instance = null;

    private Singleton(){}

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

错误解析
  由于指令重排优化,可能会导致初始化单例对象将该对象地址赋值给 instance 字段的顺序与上面 Java 代码中书写的顺序不同。例如,线程 A 在创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时线程 A 就可以将分配的内存地址赋值给 instance 字段了,然而该对象可能还没有初始化。线程B来调用 getInstance() 方法,得到的就是未初始化完全的单例对象,这就会导致系统出现异常行为。

备注:上面所说的初始化单例对象将该对象地址赋值给 instance 字段是指:instance = new Singleton()。这段代码涉及到两个指令,初始化单例对象:new Singleton();将该对象地址赋值给 instance 字段:instance = 内存地址在构造方法被调用之前就是指new Singleton()

  为了解决该问题,我们可以使用 volatile 关键字修饰 instance 字段。volatile 关键字的一个语义就是禁止指令的重排序优化,从而保证 instance 字段被初始化时,单例对象已经完全被完全初始化。最终得到的代码如下所示:

public class Singleton {
    /**
     * 使用 volatile 关键字修饰,避免指令重排序
     */
    private volatile static Singleton instance = null;

    private Singleton(){}

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

二、静态内部类

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

熟悉 Java 类加载机制的都知道,当第一次访问类中的静态字段时,会触发类加载,并且同一个类只加载一次。静态内部类也是如此,类加载过程由类加载器负责加锁,从而保证线程安全。此种单例模式更加简洁明了,不容易出错。

发布了444 篇原创文章 · 获赞 113 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/panchang199266/article/details/103222344