DCL-单例模式的线程安全

DCL-Double Check Lock

双端检锁机制

传统单机环境下的单例模式

public class Test002 {

    private static Test002 instance = null;

    private Test002(){
        System.out.println(Thread.currentThread().getName() + "	这是一个构造器" );
    }

    public static Test002 getInstance(){
        if(instance == null){
            instance = new Test002();
        }

        return instance;
    }

    public static void main(String[] args) {
        System.out.println(Test002.getInstance() == Test002.getInstance());
        System.out.println(Test002.getInstance() == Test002.getInstance());
        System.out.println(Test002.getInstance() == Test002.getInstance());
    }
}

输出结果

多线程环境下

public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
              Test002.getInstance();
            },String.valueOf(i)).start();
        }
    }

输出结果

解决方法

传统可以加sync,但是sync是重锁,存在很大的弊端,直接把整个方法getInstance()加锁了
所以我们采用DCL-双端检锁机制,只给代码块加sync

public static Test002 getInstance(){

        if(instance == null){
            synchronized(Test002.class){
                if(instance == null){
                    instance = new Test002();
                }
            }
        }
        return instance;
    }

但是这样会出现指令重排,高并发多线程环境下,底层会对指令进行优化,导致顺序发生改变,可能会出现问题
原因是某一个线程执行到第一次检测时,读取到的instance不为null时,instance的引用对象可能还没有完成
初始化(但是内存已经被分配出去了)

instance = new Instance()可以分成以下三步:

memory = allocate();// 1.分配对象内存空间
instance(memory);// 2.初始化对象
instance = memory;// 3.设置instance指向刚刚分配的内存地址,此时instance!=null;

步骤2,3不存在数据依赖关系,所以可以进行重排优化
因此,需要加volatile修饰

private static volatile Test002 instance = null;
原文地址:https://www.cnblogs.com/zhangyuanbo/p/14188532.html