`
六十三
  • 浏览: 44205 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

理解静态变量惰性初始化的双检锁模式

阅读更多

 

 对于双检锁,其实有多种不同的用法,有很多种用法是无论如何不会出现问题的.我最初用双检锁来获取jndi对象时,立即有人告诉我双检锁是不安全的,我笑着告诉他:是否安全我比你更有把握.

 

static DataSource ds = null;
public static DataSource getDataSource(){
 if(ds == null){
  synchronized(this.getClass()){
   if(ds == null)
    ds = xxx;
  }
 }
 return ds;
}

 

 

       这样的DCL有什么安全问题呢?它仅仅是为了不做重复的劳动.一是ds本身是已经存在的对象,不是动态构造的,二是即使多次获取它也还是同一引用.这里做的工作仅仅是不想让另一个线程多做在JNDI上再查找一次的工作,因为查找本身是耗时的,与其让另一个线程再查找还不如把它阻塞在synchronized外面什么也不做.事实上即使是再查找一次两次获得的还是相同的引用,而且是已经构造好,不存在初始化问题的对象.DCL会有什么安全问题?

       所以不要一看到双检锁就认为它有安全问题.有些时候它完全可以非常好地工作.只是你要理解它的安全问题到底在哪儿?

DCL到底有什么问题?

       DCL的安全性最初被公开的时候在2001年JavaWorld.其实那个例子在那个时候已经是错误的.影响了后面几代人都跟着错误.有时候我甚至不敢相信一些非常简单的问题因为放在权威的地方就没有人敢去责疑.最初的问题提出者在这儿:http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html.例子是:

 

class SomeClass {  
  private Resource resource = null;
  public Resource getResource() {
    if (resource == null) {
      synchronized {
        if (resource == null)
          resource = new Resource();
      }
    }
    return resource;
  }
}

        我不知道提这个问题的人是否是个老古董.在2001年,JVM1.2已经发布好久了.JMM(Java Memory Model)已经已经发布了新的规范,竟然在这个时候提出这样的问题.而且这个已经不存在的问题在此后的五年(今年是2006年)中一误导着很多人,甚至是一些博士.

 

       这个问题的提出者是这样说的.当一个线程运行到resource = new Resource();时,因为new一个对象需要分配空间,初始化字段,调用构造方法.当为resource分配好空间后,外面的其它线程就可以看到不为null的resource,而这时有可能还没有初始化字段和调用构造方法就被其它线程引用了.确实,在JAVA2(以jdk1.2开始)以前对于实例字段是直接在主储区读写的.所以当一个线程对resource进行分配空间,初始化和调用构造方法时,可能在其它线程中分配空间动作可见了,而初始化和调用构造方法还没有完成.

       但是从JAVA2以后,JMM发生了根本的改变,分配空间,初始化,调用构造方法只会在线程的工作存储区完成,在没有向主存储区复制赋值时,其它线程绝对不可能见到这个过程.而这个字段复制到主存区的过程,更不会有分配空间后没有初始化或没有调用构造方法的可能.在JAVA中,一切都是按引用的值复制的.向主存储区同步其实就是把线程工作存储区的这个已经构造好的对象有压缩堆地址值COPY给主存储区的那个变量.这个过程对于其它线程,要么是resource为null,要么是完整的对象.绝对不会把一个已经分配空间却没有构造好的对象让其它线程可见.

       至于不同线程如何看到另一个线程对实例字段的改变,可以参看我的另一篇文章:深入理解JMM.

那么上面的例子是否有问题?
       我可以负责地说,在JVM1.2以后,上面的例子没有问题.(也许还是有问题但以我的水平还没有发现,但绝对不是JW上提出的那样的问题,那个问题只在1.2以前出现,而2001年早已不是jdk1.1的时代了),很多时候我们需要有独立的思考,当你觉得你获取一个新知识点时你一定要弄清它存在的环境.那么到底DCL是否真的就没有问题了呢?否,现在的问题还是可见性问题,但问题转到了同一对象不同字段上面,这个问题已经在以前说过了:

public MyObject{    
    private static MyObect obj;
    private Date d = new Data();
    public Data getD(){return this.d;}
    public static MyObect  getInstance(){
        if(obj == null){
            synchronized(MyObect .class){
                if(obj == null)
                    obj = new MyObject();//这里
            }
        }
        return obj;
    }
}

 

       一个线程A运行到"这里"时,对于A的工作区中,肯定已经产生一个MyObect对象,而且这时这个对象已经完成了Data d.现在线程A调用时间到,执行权被切换到另一个线程B来执行,会有什么问题呢?如果obj不为null,线程B获得了一个obj,但可能obj.getD()却还没有初始化.为什么?既然obj已经可见了(线程A还没有离开同步块),而d却不可见呢?如果d不可见,那么obj也应该为null啊?线程B应该等待A释放同步块啊?事实上,对于"这里"这条语句,线程A还没有离开同步块.因为没有"离开同步块"这个条件,线程a的工作区没有强制与主存储器同步,这时工作区中有两个字段obj,d 到底先把谁同步到主存储区,没有条件限制,虽然在线程A的工作区obj和d都是完整的,但有JSL没有强制不允许先把obj映射到主存储区,如果哪个jvm实现按它的优化方案先把工作存储器中的obj同步到主存储器了,这时正好线程B获取了,而d却没有同步过去,那么线程B就获取了obj的引用却找不能obj.getD();

       我们发现,其实对于安全性问题都是基于即时构造对象这样的条件下的.如果你把DCL用来控制其它不重复操作,它就不会出现这样的问题,就象上面从JNDI中查找DataSource,因为查找本身是耗时的,所以我用DCL来控制"查找"这个行为而不是控制对象本身.其实还有很多可以正确应用DCL的地方.象JLive,OFBiz这些开源项目中都在大量应用DCL,当然我不是推荐大家都去用DCL,而是如果需要而且合适就去用.有些问题可以用其它方法来代替.只要你能真正明白它在什么时候是不安全的,你就可以安全地使用DCL.

 

分享到:
评论

相关推荐

    lazy-static.rs:一个用于在Rust中定义惰性求值静态变量的小宏

    使用此宏,可能具有static ,这些static要求在运行时执行代码才能进行初始化。 这包括需要堆分配的任何内容,例如向量或哈希图,以及需要计算非const函数调用的所有内容。最小支持的rustc 1.27.2+ 此版本已在CI中...

    设计模式可复用面向对象软件的基础.

    - 创建型模式还包括:抽象工厂模式、建造者模式、原型模式和惰性初始化模式。 - 结构型模式涉及如何组合现有类和对象以形成更大的结构,包括适配器模式、桥接模式、装饰器模式、外观模式、组合模式、享元模式和代理...

    设计模式之单例模式

    3. **惰性初始化**:只有当确实需要时才进行实例化。 4. **线程安全**:在多线程环境下也能正确工作。 #### 使用场景 - 数据库连接管理:通过单例模式可以有效地控制数据库连接资源,避免频繁地打开和关闭连接导致...

    Once_cell:Rust库,用于单个分配单元和无宏的惰性静态

    Once_cell库实现了这个特性,使得开发者可以在无需宏的情况下创建惰性初始化的静态变量,这大大提高了代码的可读性和可维护性。 Once_cell库提供了两个主要的类型:`OnceCell<T>`和`Lazy<T>`。`OnceCell<T>`类型...

    lazy-static.rs, 在 Rust 中,用于定义惰性计算的static 变量的小宏.zip

    lazy-static.rs, 在 Rust 中,用于定义惰性计算的static 变量的小宏 lazy-static.rs在 Rust 中声明延迟求值的静态的宏。使用这里宏,可以以使 static s 在运行时要求执行代码,以便初始化。 这包括需要堆分配,如...

    最常用的设计模式

    2. **惰性初始化**:只有当真正需要时才会创建实例。 3. **线程安全性**:在多线程环境中能够安全地创建实例。 **实现方式:** 1. **构造函数私有化**:防止外部直接创建对象。 2. **静态成员变量存储实例**:通常...

    java代码优化编程共11页.pdf.zip

    5. **延迟初始化和惰性加载**:只有在真正需要时才初始化对象,例如使用Double-Checked Locking或枚举单例实现。 6. **并发优化**:在多线程环境下,使用并发集合类如ConcurrentHashMap,避免synchronized关键字的...

    .Net 单例模式(Singleton)

    实现单例模式的方法有很多,其中一种简单的方式是惰性实例化,这种方式直到对象被要求时才进行初始化。但是,这种实现并不适合多线程环境,因为在多个线程同时执行实例检查和实例化的过程中,可能会产生多个实例,...

    kotlin-in-chinese kotlin中文版教程

    Kotlin的惯用法包括高阶函数、扩展函数、惰性初始化、委托属性等。高阶函数允许函数作为参数传递或返回,增强了代码的灵活性。扩展函数可以为已有类添加新功能,而无需继承或使用装饰者模式。惰性初始化确保对象只在...

    linux动态链接机制

    1. **初始化动态链接器**:当一个动态链接的应用程序启动时,动态链接器首先被初始化。 2. **加载动态库**:根据程序的依赖关系,动态链接器加载相应的动态库到内存中。 3. **符号解析**:动态链接器解析程序中未...

    快学Scala 课后习题答案集合

    8. **延迟初始化**:Scala提供了延迟初始化(lazy)关键字,确保变量只在首次使用时才进行初始化,这在处理大对象或昂贵计算时非常有用。 9. **隐式转换**:Scala的隐式转换可以将一种类型对象转换为另一种类型,但...

    js代码-JS惰性求值

    通过将计算过程放在getter中,我们可以实现惰性初始化。只有当尝试访问属性时,计算才会进行。例如,我们可以创建一个对象,其中某个属性的值是计算密集型的,但只有在首次访问该属性时才进行计算。 4. **Proxy和...

    Java性能优化的45个细节

    34. **使用惰性初始化**:只有在真正需要时才初始化对象,降低启动时的内存压力。 35. **使用Profile工具**:如VisualVM、JProfiler等,找出性能瓶颈。 36. **避免过度的数据库查询**:优化SQL语句,使用索引,...

    提高你c#的50各方法

    29. **自动属性与初始化器**:使用自动属性简化代码,理解初始化器的用法。 30. **异步流(Async Streams)**:了解C# 8.0引入的异步流,处理大量数据的异步读写。 31. **枚举解析与枚举转换**:理解如何安全地将...

    详解Ruby设计模式编程中对单例模式的运用

    ### 详解Ruby设计模式编程中对单例模式的运用 #### 概述 单例模式是一种常见的设计模式,它的核心思想在于确保一个类只有一个实例,并且这...此外,理解类变量与实例变量的区别有助于更好地掌握单例模式的内部机制。

    more effective c++

    应确保构造过程中的所有成员都已正确初始化后再进行任何虚函数调用。 #### 1.2 项MM2:在C++中使用C风格的字符串时要小心 C风格的字符串(以null字符结尾的字符数组)在C++中使用时存在诸多风险,如数组大小不明确...

    Java程序性能优化之二十三个建议

    14. **使用惰性初始化**:对于静态字段,只有在第一次使用时才初始化,降低启动时间。 15. **避免在循环中调用方法**:如果可能,将方法调用的结果存储在变量中,减少方法调用的开销。 16. **代码优化与重构**:...

    ThinkPHP5.0手册.pdf

    它也介绍了控制器的自动定位、请求控制器定义、控制器初始化、前置操作、跳转和重定向、空操作、空控制器、多级控制器、分层控制器、Rest控制器、HTTP头信息、请求信息、输入变量、更改变量、请求类型、请求伪装、伪...

    ThinkPHP5.0完全开发手册.pdf

    - 模型的初始化、定义新增更新删除查询、聚合、获取器、修改器、时间戳、软删除、类型转换、数据完成查询范围、模型分层、数组访问和转换、JSON序列化、事件关联、一对一关联、一对多关联、远程一对多、多对多关联、...

    Haskell: Functional Programming with Types

    - **声明与赋值**:如何在Haskell中声明和初始化变量。 - **不可变性**:探讨变量不可变性带来的好处及其实现机制。 - **源文件管理**(Haskell source files): - **模块化**:介绍如何组织Haskell程序为多个...

Global site tag (gtag.js) - Google Analytics