`
jilen
  • 浏览: 98337 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Double Check Locking -- Java并发

阅读更多

the double-check locking broken  主要解释下内存模型相关的这篇文章

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

 

 

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo { 
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) 
      synchronized(this) {
        if (helper == null) 
          helper = new Helper();
      }    
    return helper;
    }
  // other functions and members...
  }

 

双检查锁,应用范围很广,很多开源项目,spring,tomcat都能找到。但以上算法是有问题。

出现问题的原因之一:暴露未构造完整的对象。由于编译器或者cpu指令优化,可能导致出现,将未构造完整的Helper对象

引用赋值给 helper字段。假设Helper有个int 属性id = 100。就是说客户端获取到的这个helper,id的值可能是0(没有被正确初始化,即未被构造完整)。解决方法是加volatile。volatile有效防止reorder,从而保证赋值前对象是完整构造过的。

4
2
分享到:
评论
13 楼 Silverside 2011-08-08  
jilen 写道
skzr.org 写道
以下内容是我当时学习时整理的双检查:

1. 延迟实例化:使用正确的双检查模式,需要new Instance的的必须如此操作

实例:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) instance = new T();
                }
        }
        return instance;
}
问题: instance = new T(); 当new的时候,做了两个操作
第一步申请内存,并且把引用给instance(此时instance!=null)
第二步调用构造器,初始化数据
所以:其他线程可能使用了这个不完整的实例(虽然new已经分配了内存,从而instance!=null, 但可能此对象还在构造,所以说instance实例还不完整)
解决:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) {
                                T t = new T();
                                instance = t;//<----解决办法
                        }
                }
        }
        return instance;
}
注意:其他非instacne = new T()这样的,可以不用这么处理,比如: instance = existObj.getT();也就是任何非new的都不会出问题

我想你可能有些误解 Object t = new Object();这个表达式做文法分析的时候很显然是先构造后赋值的。之所以出现先赋值后构造,是编译器或者cpu指令优化(smp)才出现的偶然情况,而且发生之概率也是很低的。如果编译器发现构造器没有抛出任何异常,也没有任何同步,则可能因为优化指令等原因进行reorder,打乱赋值和初始化顺序。即使编译器指令正常,现代CPU也可能会在这些指令执行时reorder造成乱序。我想你的fix不能真的fix这个问题。

这个时候构造器会报什么异常呢?
12 楼 jilen 2011-05-28  
scanfprintf123 写道
jilen 写道
skzr.org 写道
以下内容是我当时学习时整理的双检查:

1. 延迟实例化:使用正确的双检查模式,需要new Instance的的必须如此操作

实例:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) instance = new T();
                }
        }
        return instance;
}
问题: instance = new T(); 当new的时候,做了两个操作
第一步申请内存,并且把引用给instance(此时instance!=null)
第二步调用构造器,初始化数据
所以:其他线程可能使用了这个不完整的实例(虽然new已经分配了内存,从而instance!=null, 但可能此对象还在构造,所以说instance实例还不完整)
解决:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) {
                                T t = new T();
                                instance = t;//<----解决办法
                        }
                }
        }
        return instance;
}
注意:其他非instacne = new T()这样的,可以不用这么处理,比如: instance = existObj.getT();也就是任何非new的都不会出问题

我想你可能有些误解 Object t = new Object();这个表达式做文法分析的时候很显然是先构造后赋值的。之所以出现先赋值后构造,是编译器或者cpu指令优化(smp)才出现的偶然情况,而且发生之概率也是很低的。如果编译器发现构造器没有抛出任何异常,也没有任何同步,则可能因为优化指令等原因进行reorder,打乱赋值和初始化顺序。即使编译器指令正常,现代CPU也可能会在这些指令执行时reorder造成乱序。我想你的fix不能真的fix这个问题。




如果出现了重排序,导致先赋值后进行构造了,即使加了volatile,能避免吗?

从Happen-before 关系并不能推出这个, 即使是从http://gee.cs.oswego.edu/dl/jmm/cookbook.html 中所写防止reorder关系,也推不出来,

    // Broken multithreaded version  
    // "Double-Checked Locking" idiom  
    class Foo {   
      private volatile Helper helper = null;  
      public Helper getHelper() {  
        if (helper == null)   
          synchronized(this) {  
            if (helper == null)   
              helper = malloc(); // other thread can see the reference but not the status of helper, since constructor haven't happened yet.
              //constructor happens here
          }      
        return helper;  
        }  
      // other functions and members...  
      }  



JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write.
不会发生reorder,所以构造总是在赋值之前。
11 楼 scanfprintf123 2011-05-27  
jilen 写道
skzr.org 写道
以下内容是我当时学习时整理的双检查:

1. 延迟实例化:使用正确的双检查模式,需要new Instance的的必须如此操作

实例:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) instance = new T();
                }
        }
        return instance;
}
问题: instance = new T(); 当new的时候,做了两个操作
第一步申请内存,并且把引用给instance(此时instance!=null)
第二步调用构造器,初始化数据
所以:其他线程可能使用了这个不完整的实例(虽然new已经分配了内存,从而instance!=null, 但可能此对象还在构造,所以说instance实例还不完整)
解决:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) {
                                T t = new T();
                                instance = t;//<----解决办法
                        }
                }
        }
        return instance;
}
注意:其他非instacne = new T()这样的,可以不用这么处理,比如: instance = existObj.getT();也就是任何非new的都不会出问题

我想你可能有些误解 Object t = new Object();这个表达式做文法分析的时候很显然是先构造后赋值的。之所以出现先赋值后构造,是编译器或者cpu指令优化(smp)才出现的偶然情况,而且发生之概率也是很低的。如果编译器发现构造器没有抛出任何异常,也没有任何同步,则可能因为优化指令等原因进行reorder,打乱赋值和初始化顺序。即使编译器指令正常,现代CPU也可能会在这些指令执行时reorder造成乱序。我想你的fix不能真的fix这个问题。




如果出现了重排序,导致先赋值后进行构造了,即使加了volatile,能避免吗?

从Happen-before 关系并不能推出这个, 即使是从http://gee.cs.oswego.edu/dl/jmm/cookbook.html 中所写防止reorder关系,也推不出来,

    // Broken multithreaded version  
    // "Double-Checked Locking" idiom  
    class Foo {   
      private volatile Helper helper = null;  
      public Helper getHelper() {  
        if (helper == null)   
          synchronized(this) {  
            if (helper == null)   
              helper = malloc(); // other thread can see the reference but not the status of helper, since constructor haven't happened yet.
              //constructor happens here
          }      
        return helper;  
        }  
      // other functions and members...  
      }  


10 楼 jilen 2011-05-22  
Object t = new Object();
another = t;这样的语句在做语义分析后产生的文法树,跟
another = new Object()很可能是一样的。所以我想你看的书或许有些问题
9 楼 jilen 2011-05-22  
注意,编译器可能会对构造方法调用做内联(inline)所以很可能不是通过堆栈做初始化,而是直接将初始化指令内联到代码中。所以这个时候出现reorder的情况是有可能的
8 楼 jilen 2011-05-22  
skzr.org 写道
以下内容是我当时学习时整理的双检查:

1. 延迟实例化:使用正确的双检查模式,需要new Instance的的必须如此操作

实例:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) instance = new T();
                }
        }
        return instance;
}
问题: instance = new T(); 当new的时候,做了两个操作
第一步申请内存,并且把引用给instance(此时instance!=null)
第二步调用构造器,初始化数据
所以:其他线程可能使用了这个不完整的实例(虽然new已经分配了内存,从而instance!=null, 但可能此对象还在构造,所以说instance实例还不完整)
解决:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) {
                                T t = new T();
                                instance = t;//<----解决办法
                        }
                }
        }
        return instance;
}
注意:其他非instacne = new T()这样的,可以不用这么处理,比如: instance = existObj.getT();也就是任何非new的都不会出问题

我想你可能有些误解 Object t = new Object();这个表达式做文法分析的时候很显然是先构造后赋值的。之所以出现先赋值后构造,是编译器或者cpu指令优化(smp)才出现的偶然情况,而且发生之概率也是很低的。如果编译器发现构造器没有抛出任何异常,也没有任何同步,则可能因为优化指令等原因进行reorder,打乱赋值和初始化顺序。即使编译器指令正常,现代CPU也可能会在这些指令执行时reorder造成乱序。我想你的fix不能真的fix这个问题。
7 楼 skzr.org 2011-05-21  
以下内容是我当时学习时整理的双检查:

1. 延迟实例化:使用正确的双检查模式,需要new Instance的的必须如此操作

实例:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) instance = new T();
                }
        }
        return instance;
}
问题: instance = new T(); 当new的时候,做了两个操作
第一步申请内存,并且把引用给instance(此时instance!=null)
第二步调用构造器,初始化数据
所以:其他线程可能使用了这个不完整的实例(虽然new已经分配了内存,从而instance!=null, 但可能此对象还在构造,所以说instance实例还不完整)
解决:

public static T getInstance() {
        if (instance == null) {
                synchronized (T) {
                        if (instance == null) {
                                T t = new T();
                                instance = t;//<----解决办法
                        }
                }
        }
        return instance;
}
注意:其他非instacne = new T()这样的,可以不用这么处理,比如: instance = existObj.getT();也就是任何非new的都不会出问题
6 楼 skzr.org 2011-05-21  
class Foo {  
  private Helper helper = null; 
  public Helper getHelper() { 
    if (helper == null)  
      synchronized(this) { 
        if (helper == null)  
         Helper tmp = new Helper(); 
          helper = tmp;

      }     
    return helper; 
    } 
  // other functions and members... 
  }
从class字节码是看不出问题的,这个问题是运行时才会可能发生。

这个双检查可能导致的问题确实是 暴露未构造完整的对象,因为new操作和构造器调用的时序有点小问题。getHelper()可能获得的是一个未构造完整的对象。


注意看上面的代码: Helper tmp = new Helper(); 
          helper = tmp;

传说中是可以保证正确构造的。记得有本书介绍这个双检查时提到了怎样解决这个问题。
5 楼 skzr.org 2011-05-21  
class Foo {  
  private Helper helper = null; 
  public Helper getHelper() { 
    if (helper == null)  
      synchronized(this) { 
        if (helper == null)  
          helper = new Helper(); 
      }     
    return helper; 
    } 
  // other functions and members... 
  } 
4 楼 Technoboy 2011-05-20  
jilen 写道
Technoboy 写道
因为JMM的原因,helper可能在constructor被调用之前就被存储,如果此时另外一个thread调用getHelper方法且在helper的constructor调用前,那么就有这种id为0的可能。加volatile关键字,问题可以解决,但是helper会被初始化一次以上。


我想不会初始化一次以上不论如何能进入synchronized块得线程只能有一个,这个线程执行期间其他试图进入synchronized块线程都阻塞,当线程执行完毕释放锁时,其他试图进入synchronized块得线程可以获得锁,但此时helper已经不是null了

恩,不好意思,我表述错了,你说的对,我和你是一个意思,我想解释在不加volatile关键字的情形,结果说返了,sorry
3 楼 jilen 2011-05-20  
Technoboy 写道
因为JMM的原因,helper可能在constructor被调用之前就被存储,如果此时另外一个thread调用getHelper方法且在helper的constructor调用前,那么就有这种id为0的可能。加volatile关键字,问题可以解决,但是helper会被初始化一次以上。


我想不会初始化一次以上不论如何能进入synchronized块得线程只能有一个,这个线程执行期间其他试图进入synchronized块线程都阻塞,当线程执行完毕释放锁时,其他试图进入synchronized块得线程可以获得锁,但此时helper已经不是null了
2 楼 Technoboy 2011-05-20  
因为JMM的原因,helper可能在constructor被调用之前就被存储,如果此时另外一个thread调用getHelper方法且在helper的constructor调用前,那么就有这种id为0的可能。加volatile关键字,问题可以解决,但是helper会被初始化一次以上。
1 楼 witcheryne 2011-05-20  
从来没意识到过这种问题....

volatile 这个关键字, 还是最近在 《Effective Java》  中看到的

相关推荐

    计算机后端-Java-Java高并发从入门到面试教程-发课程资料.zip

    - **Double-Check Locking**:理解双重检查锁定模式,以及其在单例模式中的应用。 - ** volatile与synchronized的区别**:深入探讨两者的异同。 通过本课程的学习,开发者不仅能掌握Java并发编程的基础知识,还能...

    Java并发编程设计原则和模式

    4. 双重检查锁定模式(Double-Check Locking):用于安全地创建单例,防止多线程环境下的多次实例化。 5. 原子操作模式:使用java.util.concurrent.atomic包中的原子类,如AtomicInteger,提供无锁的并发操作。 四...

    Java并发体系.pdf

    在实际应用中,如DCL(Double Check Locking)单例模式,我们需要关注重排序可能导致的问题。为了解决这个问题,我们可以使用volatile关键字来禁止重排序,或者利用类加载机制,通过类初始化阶段的锁来确保线程安全...

    一本经典的多线程书籍 Java并发编程 设计原则与模式 第二版 (英文原版)

    - 双重检查锁定(Double-Check Locking):优化单例模式,防止不必要的同步。 5. **并发编程实践** - 线程池的最佳实践:根据任务性质和系统资源动态调整线程池大小。 - 异常处理:在并发环境中,异常可能导致...

    Java并发编程的设计原则与模式

    3. **单例模式**:在多线程环境中,保证只有一个实例存在,通常使用双重检查锁定(Double-Check Locking)或静态内部类等方式实现。 4. **线程池模式**:通过ThreadPoolExecutor管理线程,可以有效控制并发数量,...

    23种设计模式知识要点--更多Java进阶 www.cx1314.cn1

    线程安全的单例模式通常会使用双重检查锁定(Double-Check Locking)来避免同步开销。在非线程安全的实例中,如果没有正确处理并发,可能会导致多个实例的创建。解决这个问题的方法是在getInstance()方法前添加...

    java并发编程实战源码,java并发编程实战pdf,Java源码.zip

    - **双检锁/双重校验锁(DCL,Double-Check Locking)**:用于创建单例模式,保证线程安全。 - **读写锁**:在多读少写的情况下提高性能,如`ReadWriteLock`接口及其实现类`ReentrantReadWriteLock`。 6. **线程...

    java面试题_高并发、高可用、分布式(9题)

    - **单例模式**:在多线程环境下,双重检查锁定(Double-Check Locking)是实现线程安全的单例模式。 5. **JVM优化** 面对高并发场景,优化JVM的性能至关重要。这包括调整内存分配(堆、栈、元空间等),设置垃圾...

    【Java面试资料】-(机构内训资料)上海-拼多多-Java高级

    - 双重检查锁定(Double-Check Locking)与初始化-on-demand holder类设计模式。 5. **IO流与NIO** - 流的分类:字节流与字符流,输入流与输出流,缓冲流与转换流。 - 文件操作:File类的常用方法,文件复制与...

    c++ and Peris of Double Checked Locking

    虽然C++标准规定了顺序点,在这些点上不允许进行指令重排序,但DCLP中的“double check”发生在两次检查之间,这里并没有明确的顺序点。因此,编译器可能在两次检查之间重新排序代码,导致线程安全问题。 #### 解决...

    java并发编程学习思维导图

    - **线程安全初始化**:使用Double-Check Locking或Initialization-on-Demand Holder Class模式初始化单例。 - **避免长时间阻塞操作**:尽量减少I/O、网络等可能导致长时间阻塞的操作在主线程中执行。 以上只是...

    【面试资料】-(机构内训资料)Java并发编程面试题.zip

    - 双重检查锁定(DCL,Double-Check Locking)模式实现单例。 4. **锁机制** - `ReentrantLock`可重入锁,比`synchronized`更灵活,支持公平/非公平策略,提供条件变量。 - `ReadWriteLock`读写锁,允许多个读...

    java高并发1

    Java中的线程安全单例通常采用双重检查锁定(Double-Check Locking)模式,如上述代码所示,它在保证线程安全的同时提高了性能。 总的来说,Java高并发编程涉及众多知识点,包括并发与并行的区别、线程的创建与管理...

    Java并发编程:设计原则与模式(Concurrent.Programming.in.Java)(中英版)

    `synchronized`关键字或`Double-Check Locking`策略可用于实现线程安全的单例。 5. **守护线程模式**:用于后台服务,当所有非守护线程结束时,程序退出。Java中的`Thread.setDaemon(true)`方法可以将线程设置为...

    java高并发编程源码.zip

    - 双重检查锁定模式 (Double-Check Locking) 通过分析`hakesashou`这个文件名,可能是一个名为“筷子手”(谐音)的并发工具或框架,具体功能需要查看源码来了解。学习和理解这些Java高并发编程的知识点,将有助于...

    java并发规范(线程及锁).docx

    推荐使用`Double-Check Locking`或者`java.util.concurrent.atomic.AtomicReference`来实现线程安全的延迟初始化。 遵循这些规范,开发者可以编写出更健壮、高效的并发代码,避免潜在的线程安全问题,提高程序的...

    Java并发编程(18)第五篇中volatile意外问题的

    在某些复杂的并发场景下,如双重检查锁定(Double-Check Locking)模式,单纯依赖volatile可能导致“幻象实例”问题,这时需要结合synchronized来保证正确初始化单例。 文档可能还分析了volatile变量在循环中的使用...

    Java Concurrency In Practice Learning Note

    - **双检锁/双重校验锁(DCL,Double-Check Locking)**:用于单例模式,保证多线程环境下的正确初始化。 8. **并发性能调优** - **线程池大小的设置**:根据系统资源和应用需求合理配置线程池的大小。 - **并发...

    java代码-double check单例模式

    **Java代码 - 双重检查锁定(Double-Check Locking)单例模式** 在Java编程中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景下非常...

    尚硅谷大厂面试题第二季周阳主讲整理笔记

    在Java中,DCL(Double Check Locking)是一种常见的实现方式,但在多线程环境下,如果不使用volatile,可能存在指令重排导致线程安全问题。通过将instance声明为volatile,可以避免指令重排,确保线程安全。 总结...

Global site tag (gtag.js) - Google Analytics