DoubleCheck双重检查实战及原理解析

package com.learn.design.pattern.creational.singleton;/*** DoubleCheck关注的是什么呢* 双重检查* 在哪里检查* * * @author Leon.Sun**/
public class LazyDoubleCheckSingleton {/*** 我们声明volatile* 我们只要做这么一个小小的修改* 就可以实现线程安全的延迟初始化* 这样重排序就可以被禁止* 那在多线程的时候呢* CPU也有共享内存* 我们在加了volatile关键字之后* 所有线程都能够看到共享内存的执行状态* 保证了内存的可见性* 那这里面就和多线程有关了* 关于volatile修饰的共享变量呢* 在进行写操作的时候* 会多出一些汇编代码* 起到两个作用* 第一是将当前处理器缓存好的数据缓存到数据内存* 那这个写回到内存的操作呢* 回写到其他内存缓存了* 该内存的地址数据无效* 那因为其他CPU内存数据无效了* 所以他们又从共享内存共享数据* 这样呢就保证了内存的可见性* 这里面主要是用了缓存一致性协议* 那当处理器发现我这个缓存已经无效了* 所以我在进行操作的时候* 会重新从系统内存中把数据读到处理器的缓存里* 那我们就不深入讲解这块了* 再讲就要到汇编和信号的问题了* 我们的重点还是单例模式* 通过volatile和doublecheck这种方式呢* 既兼顾了性能又兼顾了线程* */private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;private LazyDoubleCheckSingleton(){}/*** 首先我们的方法不用锁了* 也就是说public static LazyDoubleCheckSingleton getInstance()这个方法一调到这里* 就立刻锁上* 而是把锁定放在方法体中* 对象还是进行一个判断* 判断完成之后* 这个时候呢* * Thread0在if(lazyDoubleCheckSingleton == null)这里* 这个instance是null* 我们切到Thread1上* Thread1进入if* 第二重判断* 我们重点关注DoubleCheck* 我们再切换到Thread0* 因为lazyDoubleCheckSingleton为空* 所以Thread0也可以进来* 但是在synchronized (LazyDoubleCheckSingleton.class)会被block掉* 那我们再切换到Thread1上* Thread1单步走* 开始new* 这个时候已经new完了* Thread1现在释放了这个锁* 所以切回到Thread0* Thread0获取这个锁之后* 进入第二个if判断* 这个时候进入第二层的空判断* 那他判断lazyDoubleCheckSingleton这个对象并不为空* 所以他直接走到这里* 然后在单步走* 走到return* 注意他后面是425* 而Thread1也是425* 因为是debug* 执行非常快* 因为我们通过volitale关键字已经去new对象的时候* 有可能出现的重排序已经解决了* 那我们直接F6单步走* 现在我们看一下console* 并且在创建对象的时候* 希望能理解doublecheck* 单例模式的一个演进* 一定要学会多线程debug的实战技能* 非常重要* 那刚刚我们也说了* 对这种重排序* 我们有两种解决方案* 第一是不允许2和3重排序* 还有一种方案允许23重排序* 但是不允许其他线程看到这个重排序* 刚刚我们是基于不允许23重排序来解决的* 接下来我们使用第二种方式来解决这个问题* 同时讲解一下原理* * * * * * @return*/public static LazyDoubleCheckSingleton getInstance(){if(lazyDoubleCheckSingleton == null){/*** 判断完成之后我们锁定这个单例的这个类* synchronized (LazyDoubleCheckSingleton.class)* 注意这里* 我们现在锁定了这个类* 那也就代表着if是进来的* 至少进到这个里面的线程到if(lazyDoubleCheckSingleton == null)这里的时候* 这个对象还是空的* 那我们想象一下* 因为if(lazyDoubleCheckSingleton == null)这里没有锁* 所以如果另外一个线程进来* 如果判断if(lazyDoubleCheckSingleton == null)它为空* 那到synchronized (LazyDoubleCheckSingleton.class)* 也会阻塞* 如果进入到这里面的* 已经把这个对象生成好了* 那刚刚新进来的线程呢* if(lazyDoubleCheckSingleton == null)判断的时候* 会直接return* 这里面还有一个小坑* 就是指定重排序的问题* 那一会讲到这里再说* 加锁之后我们肯定还要做一层空的判断* 这个lazyDoubleCheckSingleton对象如果为null的话* 这个时候我才会给他赋值* lazyDoubleCheckSingleton这个对象我们平时使用的时候* 一般命名成instance* 只不过我们这里要讲多种方式* 通过命名也加以区分* 免得弄混了* 这个instance就是单例的对象* 那我们看一下现在的这种写法* synchronized (LazyDoubleCheckSingleton.class)不加锁* 不为null就直接返回* 如果为null的话也只有一个线程进入到这里面* 这个就可以大量的减少synchronized加载方法上的时候带来的性能开销* 看上去我们的这个实现非常完美* 多个线程的时候我们通过加锁来保证只有 一个线程来创建对象* 当对象创建好之后呢* 以后再调用getInstance方法的时候* 都不会再需要加锁* 直接返回已创建好的对象* 这里面有个隐患* 那隐患出在上面的if判断* 还有lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();* 现在说一下为什么* 首先在上面的if判断的时候* 虽然判断了这个对象是不是为空* 这个时候是有可能不为空的* 虽然他不为空* 但是这个对象可能还没有完成初始化* 也就是lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();还没有执行完成* 那我们来看一下这个new的这块代码* 当我把这个对象new出来的时候* 看上去是一行* 实际上这里面经历了三个步骤* 我们加一个注释写到这里吧* 第一步分配内存给这个对象* 也就是给这个对象分配内存* 第二步初始化这个对象* 第三步设置lazyDoubleCheckSingleton指向刚分配的内存地址* 也就是这一行执行了三个操作* 那在2和3的时候* 可能会被重排序* 也就是说呢* 2和3的顺序有可能会被颠倒* 变成这样的* 先分配这个内存给这个对象* 然后单例对象指向刚分配的内存地址* 注意现在已经指向了这个内存地址* 所以这里空判断的时候呢* 并不为空* 但是这个单例对象有可能没有初始化完成* 这里面就要说一下* JAVA语言规范里面有说* 所有线程在执行JAVA程序时* 必须遵守intra-thread semantics这么一个约定* 他保证重排序不会改变单线程内的程序执行结果* 例如123这几个执行步骤* 对于单线程来说* 2和3互换位置* 其实不会改变单线程的执行结果* 所以JAVA语言规范允许那些在单线程内不会改变单线程执行结果的重排序* 也就是说2和3是允许的* 因为这个重排序可以提高程序的执行性能* 为了更好地理解呢* 画了一个图给大家看一下* * * * */synchronized (LazyDoubleCheckSingleton.class){if(lazyDoubleCheckSingleton == null){lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();//1.分配内存给这个对象
//                  //3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址//2.初始化对象
//                    intra-thread semantics
//                    ---------------//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址}}}return lazyDoubleCheckSingleton;}
}
package com.learn.design.pattern.creational.singleton;public class T implements Runnable {@Overridepublic void run() {
//        LazySingleton lazySingleton = LazySingleton.getInstance();
//        System.out.println(Thread.currentThread().getName()+"  "+lazySingleton);/*** 调用他的getInstance方法* */LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
//        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();;//        ContainerSingleton.putInstance("object",new Object());
//        Object instance = ContainerSingleton.getInstance("object");
//        ThreadLocalInstance instance = ThreadLocalInstance.getInstance();System.out.println(Thread.currentThread().getName()+"  "+instance);}
}
package com.learn.design.pattern.creational.singleton;public class T implements Runnable {@Overridepublic void run() {
//        LazySingleton lazySingleton = LazySingleton.getInstance();
//        System.out.println(Thread.currentThread().getName()+"  "+lazySingleton);/*** 调用他的getInstance方法* */LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
//        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();;//        ContainerSingleton.putInstance("object",new Object());
//        Object instance = ContainerSingleton.getInstance("object");
//        ThreadLocalInstance instance = ThreadLocalInstance.getInstance();System.out.println(Thread.currentThread().getName()+"  "+instance);}
}

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部