)
再整理一下单例模式的饿汉与懒汉还有指令重排序的知识点在并发编程中单例模式Singleton Pattern的实现方式直接反映了开发者对线程安全、加锁粒度和编译器优化的理解。一、 饿汉模式简单但“固执”饿汉模式Eager Initialization在类加载时就完成了初始化。classSingleton{privatestaticSingletoninstancenewSingleton();privateSingleton(){}publicstaticSingletongetInstance(){returninstance;}}优点绝对的线程安全。JVM 在加载类时会保证静态变量的初始化是同步的。缺点不支持懒加载。如果该实例占用内存大且程序运行期间从未被使用就会造成资源浪费。二、 懒汉模式性能与安全的博弈classSingletonLazy{privatestaticvolatileSingletonLazyinstancenull;privatestaticObjectlockernewObject();// 第一次使用这个实例的时候, 才会创建这个实例. 创建时机是更晚的.publicstaticSingletonLazygetInstance(){if(instancenull){synchronized(locker){if(instancenull){instancenewSingletonLazy();}}}returninstance;}}懒汉模式Lazy Initialization的核心思想是不到万不得已绝不创建对象。1. 为什么需要“双重校验锁”Double-Checked Locking给出的代码中使用了if (instance null)嵌套synchronized。为什么要判断两次第一次判断外层 if为了效率。如果不加这个判断每次调用getInstance()都要先竞争锁。锁的竞争是昂贵的而实际上对象创建后所有的getInstance操作都只是读取根本不需要锁。有了这一层实例创建后线程可以直接返回不用排队。加锁synchronized为了原子性。防止多个线程同时通过了第一层判断导致创建出多个实例。第二次判断内层 if为了正确性。当 A、B 两个线程都通过了外层判断A 拿到锁创建了对象随后释放锁。此时 B 拿到锁进入内部如果不进行第二次判断B 会再次创建一个新对象单例宣告失败。三、 核心悬疑为什么必须加volatile在代码中private static volatile SingletonLazy instance里的volatile是灵魂所在。如果没有它程序可能会在极端高并发下崩溃。指令重排序的“背刺”当我们执行instance new SingletonLazy();时JVM 层面其实拆分成了三步分配内存空间给对象找个地儿。执行构造方法初始化对象填入数据。将引用指向内存空间把变量名和地址关联起来。由于编译器优化这三步可能变成1 - 3 - 2。想象一下线程 A 执行了 1 和 3此时instance已经非空了指向了一块地址但2初始化还没完成。就在这时线程 B 进来了执行外层的if (instance null)。线程 B 发现instance不为空直接return instance拿去用了。悲剧发生线程 B 拿到的其实是一个“半成品”对象还没初始化完使用它会直接报错。volatile的作用它在此处通过内存屏障禁止了上述指令重排序保证了“初始化”一定发生在“赋值给引用”之前。四、 总结懒汉模式的三个要素要写出一个完美的多线程懒汉单例必须满足构造方法私有化防止外部new。双重 if 判断兼顾性能与安全。volatile 修饰防止指令重排导致的“半成品”对象问题。