单例模式
传统的写法
/** * 懒汉模式 */ public class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } /** * 饿汉模式 */ class SingletonWithHungry { private SingletonWithHungry() { } private SingletonWithHungry instance = new SingletonWithHungry(); public SingletonWithHungry getInstance() { return instance; } } /** * 枚举单例 */ public enum Girlfriend{ GIRLFRIEND; } /** * 静态内部类 */ public class SingleTon{ private SingleTon(){} private static class SingleTonHoler{ private static SingleTon INSTANCE = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.INSTANCE; } }
懒汉模式的线程不安全复现
class TestSingleWithThread { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.err.println(Singleton.getInstance()); }); Thread t2 = new Thread(() -> { System.err.println(Singleton.getInstance()); }); t1.start(); t2.start(); t1.join(); t2.join(); } }
解决方案:
- 懒人同步——getInstance方法加上synchronized关键字
- DCL——Double Checked Lock双重检查锁定
DCL:
class SingletonThreadSafeDCL { private static SingletonThreadSafeDCL instance = null; private SingletonThreadSafeDCL() { } public static SingletonThreadSafeDCL getInstance() { if (instance == null) {//(1) synchronized (SingletonThreadSafeDCL.class) {//(2) if (instance == null) {//(3) instance = new SingletonThreadSafeDCL();//(4) } } } return instance; } }
关于DCL存在的问题,参考《并发编程的艺术》3.8节。
线程执行到(1)时,代码读取到的instance不为null,instance引用的对象可能还没完成初始化
而new SingletonThreadSafeDCL()在JVM中分为三步:
1.在堆内存开辟内存空间。 2.在堆内存中实例化SingleTon里面的各个参数。 3.把对象指向堆内存空间。
由于重排序,可能导致3在2之前执行,
因此,导致线程B访问到了一个未初始化的对象。
比如单例有属性name,在新建实例时name 初始化为Michael,线程B访问到instance时,name未完成初始化,两个线程读到的值不一致。
解决DCL
- volatile,禁止重排序
扩展: volatile happens-before, volatile JMM语义
- 基于类初始化的解决方案