深入理解DCL(双重检测锁)单例

深入理解DCL(双重检测锁)单例

  1. 如果不使用双重检测锁我们可以直接使用synchronized关键字实现
// 懒汉式
public class Singleton {
		// 单例对象
    private Singleton singleton = null;
		
		// 私有构造方法
    private Singleton() {
    }
		
		// 获取单例
    public static synchronized  Singleton getSingleton() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }

		/*
			这个方法等价于上面的方法  
			public static Singleton getSingleton() {
        synchronized (Singleton.class) {
            if (null == singleton) {
                singleton = new Singleton();
            }    
        }
        return singleton;
    }
			
		*/
}

上面这种方式可以实现线程安全的懒汉式单例模式,但是有个致命的的缺点:比如现在有一万个线程,只有一个线程可以获取到synchnized锁,9999个线程全部阻塞挂起,效率及其的低下

  1. 所以我们就引入了双重检测锁的方式
public class DCLSingleton {

    // 单例
    private static DCLSingletonsingleton = null;

    // 私有构造方法
    private DCLSingleton() {
    }

    public static DCLSingleton getInstance() {
        if (null == singleton) {
            synchronized (DCLSingleton.class) {
                if (null == singleton) {
					singleton= new  DCLSingleton();
                }
            }
        }
        return singleton;
    }
}

也就是在synchronized的外层再加一次实例是否为空的判断

原因:如果第一个线程创建了实例化,其余的线程就不需要再次获取锁去内层判断实例是否为空,

这样就减少了线程挂起的过程,减少了cpu切换的开销

其实这种方法也是有问题的,在较小的并发量下并不会出现问题,只有在超大并发量下才会出现问题

在alibaba开发规范中明确指出,在使用双重检查锁时要使用volatile关键字

在这里插入图片描述

  1. 所以最完整的写法为
public class DCLSingleton {

    // 单例
    private static volatile DCLSingleton singleton = null;

    // 私有构造方法
    private DCLSingleton() {
    }

    public static DCLSingleton getInstance() {
        if (null == singleton) {
            synchronized (DCLSingleton.class) {
                if (null == singleton) {
                    singleton = new  DCLSingleton();
                }
            }
        }
        return singleton;
    }
}
  1. 解析:

首先用到idea的一个插件:可以方便的看到字节码文件

jclasslib Bytecode Viewer 使用方法可自行研究

0 aconst_null
 1 getstatic #2 <org/dcl/DCLSingleton.singleton : Lorg/dcl/DCLSingleton;>
 4 if_acmpne 39 (+35)
 7 ldc #3 <org/dcl/DCLSingleton>
 9 dup
10 astore_0
11 monitorenter
12 aconst_null
13 getstatic #2 <org/dcl/DCLSingleton.singleton : Lorg/dcl/DCLSingleton;>
16 if_acmpne 29 (+13)
19 new #3 <org/dcl/DCLSingleton>
22 dup
23 invokespecial #4 <org/dcl/DCLSingleton.<init> : ()V>
26 putstatic #2 <org/dcl/DCLSingleton.singleton : Lorg/dcl/DCLSingleton;>
29 aload_0
30 monitorexit
31 goto 39 (+8)
34 astore_1
35 aload_0
36 monitorexit
37 aload_1
38 athrow
39 getstatic #2 <org/dcl/DCLSingleton.singleton : Lorg/dcl/DCLSingleton;>
42 areturn

这里重点解析一下19 ~ 26行

其实19到26行就是new 单例对象的过程。

19 行:申请一块内存空间, 保存该对象(注意在此过程还没有对对象进行初始化,也就是调用构造方法)该对象只是占用一块内存空间,如果该单例中有变量或者对象此时是JVM虚拟机给的默认值

23行: 调用构造方法,对其进行初始化。

13,26行:就好比栈中的一个引用变量 getstatic是获得这个变量,putstatic是将这个对象指向这个变量。

如果按照正常的顺序确实是没有问题的,但是你不能保证它没有指令的重排:如果发生指令的重排,

但是可能会发生指令的重排,26行和23行可能会,交换顺序后为

19 new #3 <org/dcl/DCLSingleton>
26 putstatic #2 <org/dcl/DCLSingleton.singleton : Lorg/dcl/DCLSingleton;>
23 invokespecial #4 <org/dcl/DCLSingleton.<init> : ()V>

这是就会出现问题

比如说:如果有两个线程 A, B 线程A,执行完26后,cpu切换到B线程,这时B线程进行第一次判断就不为空,B线程拿到了一个没有初始化的对象。

如果这个单例是你的账户,可想而知。。。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里的球衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值