双重检查锁,真的靠谱吗?

先说答案,不靠谱!

一般说,单例模式加载的时候,使用了饿汉式,类一加载就初始化;使用了懒汉式,调用的时候才初始化。

懒汉式,一般又有几种线程安全的方式。其中有一种就很著名:双重锁检查。

至于由来,百度就好了~

上代码:

public class Test{

private static Singleton singleton;

public static Singleton getSingleton() {
       
		//如果第一个线程获取到了单例的实例对象, 后面的线程再获取实例的时候不需要进入同步代码块中了
		if (singleton == null) {
			// 同步代码块用的锁是单例的字节码文件对象,且只能用这个锁
			synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
}
}

很正常的双重锁检查的单例模式代码,但问题出在哪儿了呢?

再看怎么执行:

public class Test{                                                       //1 

    private static Singleton singleton;                                  //2

    public static Singleton getSingleton() {                             //3

        //如果第一个线程获取到了单例的实例对象, 
        //后面的线程再获取实例的时候不需要进入同步代码块中了
        if (singleton == null) {                                         //4 第一次检查
            // 同步代码块用的锁是单例的字节码文件对象,且只能用这个锁         
            synchronized (Singleton.class) {                             //5 加锁
                if (singleton == null) {                                 //6 第二次检查
                    singleton = new Singleton();                         //7 问题出现了!
                }                                                        //8
            }                                                            //9
        }                                                                //10
        return singleton;                                                //11
    }
}

诺,执行到第7行的时候,就是创建对象,singleton = new Singleton();   这一行可以分解为如下的3行伪代码:

memory = allocate();      //1,分配对象的内存空间

ctorInstance(memory);  //2,初始化对象

instance = memory;     //3,设置instance指向刚分配的内存地址

以上三行代码,2,3步可能会被重排序,如果重排序之后,发生的情况如下:

memory = allocate();      //1,分配对象的内存空间

instance = memory;     //3,设置instance指向刚分配的内存地址;注意!此时对象还没有被初始化!

ctorInstance(memory);  //2,初始化对象

重排序之后的执行顺序:

这时候就找到问题了,如果发生重排,线程B有可能在第4行判断singleton不为null,之后线程B开始访问singleton引用的对象,而此时这个对象还没有被A线程初始化。这不就出问题了?

知道了问题,怎么解决是接下来的问题,思路如下:

1,不允许2,3步重排序

2,即使2,3重排序,那这个过程不让其他线程知晓,对外显示为原子操作。

这两种思路,产生了两种解决方式,分别为:volatile,类初始化;

欲知后事如何,且听下回分解~撸代码去了。

备注:以上部分图片来自《Java并发编程的艺术》,如有侵权,请告知,必删。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值