资讯专栏INFORMATION COLUMN

浅谈双重检查锁定和延迟初始化

Shonim / 1200人阅读

摘要:非线程安全的双重检查锁这里看起来很完美,但是是一个错误的优化,代码在读取到不为的时候,引用的对象有可能换没有完成初始化,这样返回的是有问题的。

在Java多线程程序中,有时需要采用延迟初始化来降低初始化类和创建对象的开销,双重检查锁定是常见的延迟初始化技术,但它是一种错误的用法

双重检查锁的演进以及问题

使用syncronized实现

public synchronized static Instance getInstance() {
    if (instance == null) {
        instance = new Instance();
    }
    return instance;
}

多线程情况下性能开销比较大。

非线程安全的双重检查锁

public static Instance getInstance2() {
    if (instance2 == null) {
        synchronized (UnsafeLazyInitialization.class) {
            if (instance2 == null) {
                instance2 = new Instance();
            }
        }
    }
    return instance2;
}

这里看起来很完美,但是是一个错误的优化,代码在读取到instance2不为null的时候,instance引用的对象有可能换没有完成初始化,这样返回的instance2是有问题的。
出现这个问题的根源在什么地方?
instance2 = new Instance();这一行代码在处理器执行的时候有三部操作:
1、memory = allocate() //分配内存
2、ctorInstance(memory) //初始化对象
3、instance = memory //设置instance指向刚刚分配的内存地址

上面的三行代码中,2和3之间可能会被指令重排序。如果重排序之后的顺序为1,3,2.线程A执行2的时候,线程A判断instance2不为空,返回的instance2对象就是一个还未初始化的对象。

所以对于上面的解决思路有两种:
1、不允许2和3进行指令的重排序
3、允许2和3重排序,但是不允许其他线程看到这个重排序。

不允许2和3进行指令重排序(线程安全的双重检查锁)

/**声明为volatile之后,2和3的指令重排序会被禁止*/
public static volatile Instance instance3;
public static Instance getInstance3() {
    if (instance3 == null) {
        synchronized (UnsafeLazyInitialization.class) {
            if (instance3 == null) {
                instance3 = new Instance();
            }
        }
    }
    return instance3;
}

-基于类初始化的解决

public class InstanceFactory {
    
    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }

    public static Instance getInstance () {
        return InstanceHolder.instance;
    }
}

这个是基于JVM的特性:JVM在类初始化的时候,会执行类初始化,在执行类初始化期间,JVM会获取一把锁,这个锁可以同步多个线程对同一个类的初始化。初始化一个类,包括执行这个类的静态初始化和初始化这个类中的静态字段。根据Java语言规范,在首次发生下面的任何一种情况,一个类或接口类型将立即被初始化。
1)T的实例类型被创建
2)T是一个类, 且T中的静态方法被调用
3)T声明的一个静态字段被赋值
4)T声明的静态字段被使用

在这里,首次执行getInstance(),那么InstanceHolder会进行初始化。

任何的线程安全操作在底层都是对应指令重排序以及内存可见性的问题。操作系统才是根本啊~~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74943.html

相关文章

  • 双重检查锁定延迟始化

    摘要:基于的双重检查锁定的解决方案对于前面的基于双重检查锁定来实现延迟初始化的方案指示例代码,我们只需要做一点小的修改把声明为型,就可以实现线程安全的延迟初始化。 双重检查锁定的由来 在java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化。此时程序员可能会采用延迟初始化。但要正确实现线程安全的延迟初始化需要一些技巧,否则很容易出现问题。比如,下...

    yvonne 评论0 收藏0
  • 为什么双重检查锁模式需要 volatile ?

    摘要:注意,禁止指令重排序在之后才被修复使用局部变量优化性能重新查看中双重检查锁定代码。帮助文档双重检查锁定与延迟初始化有关双重检查锁定失效的说明 双重检查锁定(Double check locked)模式经常会出现在一些框架源码中,目的是为了延迟初始化变量。这个模式还可以用来创建单例。下面来看一个 Spring 中双重检查锁定的例子。 showImg(https://segmentfaul...

    geekzhou 评论0 收藏0
  • 单例模式总结

    摘要:如果是后者,则在执行完毕未执行之前,被线程二抢占了,这时已经是非了但却没有初始化,所以线程二会直接返回在之后双重检查锁定才能够正常达到单例效果,之前有个坑。所以,在版本前,双重检查锁形式的单例模式是无法保证线程安全的。 第一种(懒汉, 线程不安全): public class Singleton { private static Singleton instance; ...

    xorpay 评论0 收藏0
  • 双重检查锁定失效分析

    摘要:双重检查锁定以下称为已被广泛当做多线程环境下延迟初始化的一种高效手段。由于没有对这些做出明确规定,很难说是否有效。可以在中使用显式的内存屏障来使生效,但中并没有这些屏障。如果改变锁释放的语义释放时执行一个双向的内存屏障将会带来性能损失。 双重检查锁定(以下称为DCL)已被广泛当做多线程环境下延迟初始化的一种高效手段。 showImg(http://segmentfault.com/i...

    keke 评论0 收藏0
  • 单例模式与双重检查锁定(double-checked locking)

    摘要:对于而言,它执行的是一个个指令。在指令中创建对象和赋值操作是分开进行的,也就是说语句是分两步执行的。此时线程打算使用实例,却发现它没有被初始化,于是错误发生了。 1.饿汉式单例 public class Singleton { private static Singleton instance = new Singleton(); ...

    yearsj 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<