单例模式中的DCL

127 阅读1分钟

首先我先写一个单例模式,各位看官看看如何

public class SingletonDemo {
    private static SingletonDemo singletonDemo;

    public static SingletonDemo getDemo() {
        if (singletonDemo != null) {
            singletonDemo = new SingletonDemo();
        }
        return singletonDemo;
    }
}

其实我们都知道这种写法是错误的,原因就是多线程情况下 判空和 new 可能会发生错乱,所以我们改一下

public class SingletonDemo {
    private static SingletonDemo singletonDemo;

    public synchronized static SingletonDemo getDemo() {
        if (singletonDemo != null) {
            singletonDemo = new SingletonDemo();
        }
        return singletonDemo;
    }
}

嗯,我们解决了多线程下的问题,但是因为加上了synchronized,导致方法性能严重下降,那么我们再换一个方法,使用DCL试试

public class SingletonDemo {
    private static SingletonDemo singletonDemo;

    public static SingletonDemo getDemo() {
        if (singletonDemo != null) {     
            synchronized (SingletonDemo.class) { 
                if (singletonDemo == null) {
                    singletonDemo = new SingletonDemo();
                }
            }
        }
        return singletonDemo;
    }
}

这样看起来天衣无缝了呢,搞定收工。 亲,真的是这样吗?不是的 我们知道JVM实例化对象一般分为3个步骤

  1. 分配内存空间
  2. 初始化对象
  3. 将内存空间指向对象

如果 代码中 2 3 步发生指令重排序呢?

  1. 分配内存空间
  2. 将内存空间指向对象
  3. 初始化对象

image.png

如上图,线程B会访问到一个没有初始化的 singletonDemo, 那么如何解决呢?

禁止指令重排序

    private volatile static SingletonDemo singletonDemo;

    public static SingletonDemo getDemo() {
        if (singletonDemo != null) {
            synchronized (SingletonDemo.class) {
                if (singletonDemo == null) {
                    singletonDemo = new SingletonDemo();
                }
            }
        }
        return singletonDemo;
    }
}

使用classLoader模式 Java中对于每个类或者接口C都会有一个唯一的初始化锁LC与之对应

public class MyClassLoader {
    private static class MyLoader {
        public static SingletonDemo singletonDemo = new SingletonDemo();
    }
    public SingletonDemo getInstance() {
        return MyLoader.singletonDemo;
    }
}

这样利用唯一初始化锁来保证单例初始化的时候只有一个线程在且不对其他线程可见