学习路径:https://coding.imooc.com/learn/list/270.html
特点:十分【懒】,成员变量赋值为null, 让调用方来初始化 【初步写法】
public class LazySingleton { // 十分懒,类加载的时候不实例化 private static LazySingleton lazySingleton = null; // 私有构造器,是外部不能够new出对象 private LazySingleton(){} // 提供给外部一个方法获取实例 public static LazySingleton getInstance(){ // 假设现有两个线程A、B,同一时间点T发生以下过程 if (lazySingleton == null) {// A进行到此 lazySingleton = new LazySingleton(); // B进行到此 // B比A先实例化了lazySingleton的对象 // 这个时间节点A的线程已经【判断过】lazySingleton==null为true,那么同样会再创建一个对象 } return lazySingleton; } }线程不安全的隐患会产生的结果
A线程和B线程拿到的是虽然是同一个实例,但是该实例是第二次创建的。 原因:B线程还未return自己创建的实例,A线程就把lazySingleton重新指向一个新的实例,所以最终A和B线程都返回了A创造的实例。 A线程和B线程拿到了不同的实例 原因:B线程先返回了自己创建的实例,A线程后返回自己创建的实例消除隐患的方法: 【演进写法一】【synchronized】加锁 public synchronized static LazySingleton getInstance(){ if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } /* 两种写法效果相同, 但是声明在方法声明上的锁是锁住整个类, 下面这张写法锁的粒度更小 */ public static LazySingleton getInstance(){ synchronized (LazySingleton.class){ if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }在 static方法声明上加锁,相当于锁住了class文件 在普通方法声明上加锁,相当于锁住了堆内存中的对象
锁住class文件会造成性能损失,可以使用DoubleCheck的方法进行处理提高性能
【演进写法二】【DoubleCheck加锁】
优化后: 有五个线程 A B C D E 按顺序访问 getInstance() ,A 拿到了锁,B ,C 会访问锁,此时A已经把对象new出来了但是D,E访问 if (lazyDoubleCheckSingleton == null)时为 false 可以直接 return,不需要再访问锁 public static LazyDoubleCheckSingleton getInstance(){ if (lazyDoubleCheckSingleton == null) { synchronized (LazyDoubleCheckSingleton.class){ if (lazyDoubleCheckSingleton == null) { // 这个语句执行了三个步骤 lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); // 1. 分配内存地址给这个对象 // 2. 初始化对象 // 3. 对象的引用指向刚分配的内存地址 // JVM允许2步骤和3步骤重排序来提高性能,为此给这种懒汉式写法带来了隐患 } } } return lazyDoubleCheckSingleton; // 如果对象已经存在则提前return, 不需要去访问锁 }明确一个点
lazyDoubleCheckSingleton指向刚分配的内存地址,此时lazyDoubleCheckSingleton不为Null, 但是其对象未被创建
这里的隐患是,JVM重排序了步骤2和3,那么B线程执行到 lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();时候,可能存在一个时间点,对象未被初始化,A就进行了 lazyDoubleCheckSingleton == null的判断结果为false, 执行语句return lazyDoubleCheckSingleton;, 此时A线程首次访问一个B线程还未初始化的对象,程序报错
【演进写法三】【DoubleCheck加锁】【volatile关键字】
// 其他代码不变,修饰成员变量 private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;volatile禁止了指令重排,消除了DoubleCheck的隐患