多线程环境下的单例模式

2018-05-21  本文已影响9人  8813d76fee36

记录一下单例模式的几个演化版本。

版本1 - 单线程下正确的版本

public class SingleThreadedSingleton {

    /**
     * 保存该类的唯一实例
     */
    private static SingleThreadedSingleton instance;

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

    /**
     * 创建并返回实例
     * @return
     */
    public static SingleThreadedSingleton getInstance() {

        if (instance == null) { // if判断在多线程环境下形成 read-check-act 操作,非原子操作
            instance = new SingleThreadedSingleton();
        }
        return instance;
    }
}
// if判断在多线程环境下形成 read-check-act 操作,非原子操作
        if (instance == null) { 
            instance = new SingleThreadedSingleton();
        }

if判断是一个read-check-act(读取-判断-执行)操作,if之后的操作是否执行取决于前面的判断结果,而read-check-act是非原子操作,因此可能发生如下情况:
线程T1执行if判断,此时instance为null,T1进入到if体但还没有执行创建实例的语句;此时线程T2也执行if判断,instance仍然为null,T2也进入到if体。之后T1,T2分别创建了两个实例。

版本2 - 同步方法

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

版本3 - 双重检查

public class DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance;
    
    private DoubleCheckSingleton() {}
    
    public static DoubleCheckSingleton getInstance() {
        
        if (instance == null) { // 第一次检查
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

private static volatile DoubleCheckSingleton instance;

ref = allocate(DoubleCheckSingleton.class);  // 1. 分配创建对象所用内存空间
invoke(ref); // 2. 初始化ref引用的对象
instance = ref; // 3. 将对象引用写入变量

上述操作在实际执行时可能发生指令重排(临界区内操作可在临界区内重排),导致执行顺序可能是 1 -> 3 -> 2 。
如以下情景:
线程T1通过双检查发现instancenull,即开始执行instance = new DoubleCheckSingleton();操作,但此时发生了指令重排执行顺序是 1 -> 3 -> 2,当执行到3时,对于变量instance来说已经不为null了,但此时该变量引用的实例并没有初始化完毕,即该实例的实例变量都是默认值而不是构造器设置的初始值,同时线程T2执行到外层第一次if检查,发现instance变量已经不为null,直接返回,则这次返回的就是T1创建的不完全实例化的对象实例。

private static volatile DoubleCheckSingleton instance;

volatile起到了两个作用:

  1. 保障可见性
    一个线程修改了instance变量的值,其他线程可以看到该线程对它所做的修改。
  2. 保障有序性
    volatile变量能够禁止被修饰的变量的写操作于之前的任何读写操作发生指令重排,因此可以避免实例化子操作3被重排到操作2之前执行,保障了所有线程获取到的实例都是完全初始化的。

版本4 - 使用静态内部类保存外部类实例

public class InnerClassSingleton {
    
    // 私有化构造方法
    private InnerClassSingleton() {}
    
    // 使用静态内部类保存外部类实例
    static class InstanceHolder {
        static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }
}

版本5 - 使用枚举(推荐)

public enum  EnumSingleton {
    INSTANCE;

    EnumSingleton() {
    }
}

利用枚举自身是单例的特点,使用枚举类来创建目标类的单例。

上一篇下一篇

猜你喜欢

热点阅读