糟糕的双重检查加锁(DCL)

  在Java并发编程时,同步都会存在着巨大的性能开销,因此,人们使用了很多的技巧来降低同步的影响,这其中有一些技巧很好,但是也有一些技巧存在一些缺陷,下面要结束的双重检查加锁(DCL)就是有缺陷的一类。

  由于早期的JVM在性能上存在一些有待优化的地方,因此在并发编程中,延迟初始化经常被用来降低程序的开销。编写正确的延迟初始化需要使用同步,但是直接在初始化之前使用同步会对性能产生影响。所以一些人就提出了双重检查加锁,并声称能够解决这个矛盾。如图所示就是双重检查加锁的代码。

  上图代码中,对Resource资源进行延时初始化,在初始化之前需要判断资源是否已经初始化过,所以进行了第一次没有同步的非空判断,resource对象却是为空时,再来对resource对象进行初始化,并将这部分初始化的代码加了类锁进行同步。这代码咋一看确实非常好,首先在第一次非空判断的时候,没有进行同步,只要对象完成了初始化,那么代码就永远走不到同步块中,所以不会有性能问题。但是仔细分析之后,其实是有缺陷的。

  这段代码的缺陷就是线程有可能会使用一个仅被部分构造的Resource对象。双重检查加锁的问题在于,当在没有同步的情况下读取一个共享对象的时候,最坏的情况下,有可能得到的是一个失效的值。这是由于我们在判断对象是否完成初始化是,只是判断对象的引用是否非空,然后在初始化对象时,JVM会在堆上给对象分配一块内存,然后JVM会干两件事,将内存的地址作为引用分配给变量和在堆上初始化这个对象,但是这两件事的先后顺序是不确定的,因此有可能resource非空,但是对象并没有初始化完成。

  所以在实际编程中一定要注意尽量不要使用双重检查加锁,如果一定要使用,那么可以将resource声明为volatile类型,这种方式对性能的影响是很小的,并且还能保证每个线程都能获取到最新的对象。

时间: 2024-12-24 00:00:24

糟糕的双重检查加锁(DCL)的相关文章

单例模式-双重检查加锁

(参考:http://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html) 双重检查加锁: (1)既实现线程安全,又能够使性能不受很大的影响.那么什么是“双重检查加锁”机制呢? (2)所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创

java单例模式(双重检查加锁)的原因

public class Singleton{ private static Singleton instance = null;//是否是final的不重要,因为最多只可能实例化一次. private Singleton(){} public static Singleton getInstance(){ if(instance == null){ //双重检查加锁,只有在第一次实例化时,才启用同步机制,提高了性能. synchronized(Singleton.Class){ if(inst

单例陷阱——双重检查锁中的指令重排问题

之前我曾经写过一篇文章<单例模式有8种写法,你知道么?>,其中提到了一种实现单例的方法-双重检查锁,最近在读并发方面的书籍,发现双重检查锁使用不当也并非绝对安全,在这里分享一下. 单例回顾 首先我们回顾一下最简单的单例模式是怎样的? /** *单例模式一:懒汉式(线程安全) */ public class Singleton1 { private static Singleton1 singleton1; private Singleton1() { } public static Singl

单例模式的两种实现方式对比:DCL (double check idiom)双重检查 和 lazy initialization holder class(静态内部类)

首先这两种方式都是延迟初始化机制,就是当要用到的时候再去初始化. 但是Effective Java书中说过:除非绝对必要,否则就不要这么做. 1. DCL (double checked locking)双重检查: 如果出于性能的考虑而需要对实例域(注意这个属性并没有被static修饰)使用延迟初始化,就使用双重检查模式 public class Singleton { private volatile Singleton uniqueInstance; private Singleton(){

双重检查锁为什么要使用volatile字段?

双重锁的由来 单例模式中,有一个DCL(双重锁)的实现方式.在Java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才开始初始化. 下面是非线程安全的延迟初始化对象的实例代码. /** * @author xiaoshu */ public class Instance { } /** * 非线程安全的延迟初始化对象 * * @author xiaoshu */ public class UnsafeLazyInitialization { private sta

[杂谈]C++的双重检查锁并不安全

原文地址 http://www.cnblogs.com/hebaichuanyeah/p/6298513.html 一个典型的单例模式构建对象的双重检查锁如下: static Singleton * getSingleObject() { if(singleObject==NULL) { lock(); if(singleObject==NULL) { singleObject = new Singleton(); } unlock(); } return singleObject; } 该代码

双重检查锁定与延迟初始化

在Java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实现线程安全的延迟初始化需要一些技巧,否则很容易出现问题.比如,下面是非线程安全的延迟初始化对象的示例代码: public class UnsafeLazyInitialization { private static Instance instance; public static Instance getInstance() { if (instanc

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

双重检查锁定的由来在Java程序中,有时需要推迟一些高开销的对象的初始化操作,并且只有在真正使用到这个对象的时候,才进行初始化,此时,就需要延迟初始化技术.延迟初始化的正确实现是需要一些技巧的,否则容易出现问题,下面一一介绍. 方案1 public class UnsafeLazyInit{ private static Instance instance; public static Instance getInstance(){ if (instance == null){ instance

双重检查锁定的单例模式和延迟初始化

有时候需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时,常用的可能就是延迟初始化,例如:懒汉式单例模式,但是要正确的实现线程安全的延迟初始化需要一些技巧,下面是非线程安全的示例代码: public class UnsafeLazyInit { private static Instance instance ; public static Instance getInstance(){ if(instance == null ) //1.A线程执行 instance