`
yesjavame
  • 浏览: 687891 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

多线程编程 深入理解DCL的安全性

阅读更多

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

static DataSource ds = null;

public static DataSource getDataSource(){
if(ds == null){
synchronized(XXX.class){
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.

对于涉及对象初始化的DCL,从JAVA1.5以后虽然可以用volatile来修补,但已经没有任何意义。因为

比它更好的模式lazy initialization hoder可以达到相同的作用而更容易理解:

public MyObject{

private static class instanceHoder{//内部私有的类,我特别用了小写开头。

static MyObject instance = new MyObject();

}
private static MyObect obj;
private Date d = new Data();
public Data getD(){return this.d;}
public static MyObect getInstance(){

return instanceHoder.instance;
}
}

private static class instanceHoder是类的定义并不会引起初始化。只有在首次调用getInstance时才会加载

instanceHoder类然后初始化instance实例。而静态初始化是由JVM来保证线程安全的,所以整个过程都不需要同

步参与,极大地提高了性能。

分享到:
评论

相关推荐

    Java多线程编程实战指南 设计模式篇.rar

    在Java编程中,多线程是一项关键技能,它允许程序同时执行多个任务,极大地提高了程序的...通过阅读"Java多线程编程实战指南 设计模式篇.pdf",你将获得更深入的理论知识和实践技巧,为你的编程事业奠定坚实的基础。

    Java 多线程编程核心技术

    《Java 多线程编程核心技术》是一本深入探讨Java平台多线程编程的著作,旨在帮助开发者全面理解和掌握多线程编程的关键概念和技术。通过实际案例的解析,本书旨在将理论与实践相结合,使读者能够有效地应用多线程...

    Java多线程编程实战指南 设计模式篇

    总之,《Java多线程编程实战指南 设计模式篇》会深入探讨如何将Java的多线程特性与经典的设计模式相结合,以提高软件的并发性能和可维护性。通过学习这本书,读者将能够更好地应对复杂并发场景,编写出高效且健壮的...

    DCL常用设计方法

    在多线程编程中,这三个概念是理解并发控制的关键。volatile保证可见性,synchronized保证有序性,而Atomic类提供原子性操作,它们都是DCL设计方法中的重要工具。 7. 死锁和竞态条件: 在设计并发程序时,需要...

    多线程,高并发.zip

    在IT领域,多线程和高并发是两个关键概念,特别是在Java编程中,它们对于构建高效、可扩展的系统至关重要。下面将详细解释这两个概念及其在Java中的实现和应用。 多线程是指在一个应用程序中同时运行多个独立的执行...

    马士兵多线程训练营笔记

    马士兵是一位知名的IT教育专家,他的多线程训练营笔记深入浅出地讲解了这一主题,帮助开发者理解并掌握多线程的精髓。 多线程允许一个程序中有多个执行流同时运行,这样可以提高应用程序的效率和响应性。在Java中,...

    JAVA多线程模式高清版+DEMO

    在Java多线程编程中,了解和掌握以下知识点至关重要: 1. **线程的创建与启动**: - 继承`Thread`类:创建新类,重写`run()`方法,然后创建该类的实例并调用`start()`方法启动线程。 - 实现`Runnable`接口:创建...

    java多线程设计模式

    最后,异常处理在多线程编程中同样重要。每个线程都应该捕获并处理可能出现的异常,以防止异常传播导致整个程序崩溃。 总之,Java多线程设计模式是构建高性能、高并发系统的基石。通过熟练掌握各种模式并合理运用,...

    java多线程 清华大学独特详细讲解源代码

    6. **设计模式**:在Java多线程编程中,一些经典的设计模式如生产者消费者模型、线程池模式、双检锁/双重校验锁定(DCL)等,能够帮助我们构建高效、安全的并发程序。 这个"Java多线程设计模式上传文件"可能包含这些...

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

    这本书深入探讨了Java平台上的并发编程技术,是Java开发者理解和掌握多线程编程的宝贵资源。 在Java并发编程中,设计原则与模式起着至关重要的作用。它们是解决并发问题的核心工具,可以帮助开发者构建高效、可靠且...

    JAVA多线程设计模式详解

    总之,“JAVA多线程设计模式详解”全面介绍了Java多线程技术,从基础知识到高级设计模式,为读者提供了扎实的理论基础和实践经验,有助于提升Java多线程编程的能力。通过学习和实践书中的内容,开发者能够更好地应对...

    java面试题_多线程(68题)

    17. **并发工具类**:如`Executor框架`, `ForkJoinPool`, `Phaser`, `CompletableFuture`等,用于简化多线程编程。 18. **线程安全的类**:如`Atomic*`系列,提供原子操作,确保并发场景下数据的正确性。 19. **...

    java多线程设计模式详解PDF及源码

    通过阅读提供的PDF文档和分析源码,开发者能够深入理解各种设计模式在多线程环境中的应用场景和实现细节,提升编写高效、安全、可扩展的并发代码的能力。在实践中不断磨练,才能真正掌握这些知识,成为Java并发编程...

    多线程设计模式

    在IT领域,多线程设计模式是...了解并掌握多线程设计模式,能够有效解决并发编程中的各种挑战,提高程序的并发性能和稳定性。在实际开发中,合理地选择和应用这些模式,对于编写高效、健壮的多线程应用程序至关重要。

    java多线程设计模式详解(PDF及源码)

    4. **线程安全的单例模式**:在多线程环境中,确保单例的唯一性和线程安全性,可以使用`synchronized`关键字、双重检查锁定(DCL)或者`Enum`单例。 5. **读写锁模式**:`ReentrantReadWriteLock`提供了读写分离的...

Global site tag (gtag.js) - Google Analytics