锁定老帖子 主题:单例模式与双重检测
精华帖 (1) :: 良好帖 (10) :: 新手帖 (2) :: 隐藏帖 (6)
|
|
---|---|
作者 | 正文 |
发表时间:2010-04-28
babby52561 写道 http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html
看看这篇吧,你们的理解已经是老黄历了~~ 事实上,双检锁已经安全了~~ 你转贴的那篇文章里,我注意到到以下一段内容,你的结论是否不太全面。。。 那么到底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()却还没有初始化. |
|
返回顶楼 | |
发表时间:2010-04-28
babby52561 写道 http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html
看看这篇吧,你们的理解已经是老黄历了~~ 事实上,双检锁已经安全了~~ 在2001年,双检锁就已经安全了的结论不是那么容易下的吧。 请参考这篇发布于2004年的文章,里面提及了双检查锁存在的问题及JMM的修复。 http://www.ibm.com/developerworks/cn/java/j-jtp02244/ |
|
返回顶楼 | |
发表时间:2010-04-28
最后修改:2010-04-28
babby52561 写道 http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html
看看这篇吧,你们的理解已经是老黄历了~~ 事实上,双检锁已经安全了~~ 如果真像这篇文章:http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html所说那样的话,1.2或以后的版本就不会有问题了,但这个规则是JMM的规范吗?谁能够确认一下。 引用 确实,在JAVA2(以jdk1.2开始)以前对于实例字段是直接在主储区读写的.所以当一个线程对resource进行分配空间, 初始化和调用构造方法时,可能在其它线程中分配空间动作可见了,而初始化和调用构造方法还没有完成. 但是从JAVA2以后,JMM发生了根本的改变,分配空间,初始化,调用构造方法只会在线程的工作存储区完成,在没有 向主存储区复制赋值时,其它线程绝对不可能见到这个过程.而这个字段复制到主存区的过程,更不会有分配空间后 没有初始化或没有调用构造方法的可能.在JAVA中,一切都是按引用的值复制的.向主存储区同步其实就是把线程工作 存储区的这个已经构造好的对象有压缩堆地址值COPY给主存储区的那个变量.这个过程对于其它线程,要么是resource 为null,要么是完整的对象.绝对不会把一个已经分配空间却没有构造好的对象让其它线程可见. 另外,文章中的第一个双重检测是可以的,这我 也成认是没有问题,因为JDNI实例早已存在,这里的双重正像文中所说那样只是为了减少查找的次数,这与我们讨论的JMM没有关系,这只能说在不同的应用场景中是不一样的。 |
|
返回顶楼 | |
发表时间:2010-04-29
huangkemin 写道 babby52561 写道 http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html
看看这篇吧,你们的理解已经是老黄历了~~ 事实上,双检锁已经安全了~~ 你转贴的那篇文章里,我注意到到以下一段内容,你的结论是否不太全面。。。 那么到底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()却还没有初始化. 这段我也看到了,他说的和大家关注的应该不是一个问题,大家关注的是构造函数未完成前,也就是对象未被完整生成前是否会被另外的线程看到。文章中很明确的提示是在现在的JMM中,应该不存在这种情况,因为未同步的代码是不会被别的线程看到的,只有执行过构造函数的对象才会被复制到主存区。而这一段的问题是存在另一个私有对象d,因为它并不是在构造函数里初始化的,所以不能保证在MyObject的构造函数完成后(对象生成时),d被初始化了。这时在工作区里会有两个对象,d和MyObject,MyObject持有一个d的引用,当MyObject被复制到主存区的时候,d如果还没有被复制过去,就会发生d未被初始化的问题,所以我觉得这个问题应该是说,必须保证构造函数被执行后,这个对象就是可用的,事实上这也是构造函数的意义所在吧。所以我觉得这与单例的懒汉模式是否安全是没有什么关系的,而是和一个对象是否会有不安全对象被逸出有关。不知我这样理解是不是有问题~~ |
|
返回顶楼 | |
发表时间:2010-04-29
yunghe 写道 babby52561 写道 http://dev.csdn.net/author/axman/4c46d233b388419e9d8b025a3c507b17.html
看看这篇吧,你们的理解已经是老黄历了~~ 事实上,双检锁已经安全了~~ 在2001年,双检锁就已经安全了的结论不是那么容易下的吧。 请参考这篇发布于2004年的文章,里面提及了双检查锁存在的问题及JMM的修复。 http://www.ibm.com/developerworks/cn/java/j-jtp02244/ 貌似我那篇文章是2006年的~~ 2004年的文章就更老了。。。我想应该不会有多少人还在使用非常古老的jvm吧。。。毕竟JDK5.0与先前的区别是非常大的。至少我看到的关于懒汉式单例的书里只提到过去的java使用懒汉式是不安全的,没有提到现在不能这么使用~~可能也是我的不严谨,不过我想这个问题的应用空间应该也不是很大吧,因为现在很少有需要使用单例的情况了,一般都是交给容器管理。呵呵 |
|
返回顶楼 | |
发表时间:2010-04-29
《head first设计模式》上提到的是:双重检查加锁不适合1.4以及更早版本,许多jvm的voliatile关键字的实现会导致双重检查加锁的实效。如果你不能使用java 5,而必须使用旧版的java,就请不要利用此技巧实现单例模式。所以我才在前面说1.4以后,单例模式的双检查加锁是安全的。
|
|
返回顶楼 | |
发表时间:2010-04-29
总之,双重检测的问题肯定是出现过了的,不然那些专家门就不会去讨论这个问题,不过这个问题留到现今由于JMM的升级可能会逐步解决,我这里拿出来谈论只是想说以前是出现过问题的,我们在应用时应该注意自己使用的环境就是了。
|
|
返回顶楼 | |
发表时间:2010-04-29
http://www.iteye.com/topic/260515
非常经典而详尽的研究。所以说最终的问题不是所谓构造函数执行到一半这个对象不完整的问题,而是因为DCL逃避了锁机制,所以会发生看不到对象中未被同步属性的最新值,发生错误。其实大抵和我贴的那个的下面一段讲的是一个意思。这篇讲得更精确更清晰。 |
|
返回顶楼 | |
发表时间:2010-05-23
mem = allocate(); //为单例对象分配内存空间. instance = mem; //注意,instance 引用现在是非空,但还未初始化 ctorSingleton(instance); //为单例对象通过instance调用构造函数 java中赋值的优先级是最低的,这个问题可以测试: public class A { public A() { try { Thread.sleep(1000000); } catch(Exception e) { e.printStackTrace(); } } static A a = null; public static void main(String[] args) throws Exception { new Thread() { public void run() { a = new A(); } }.start(); while (true) { System.out.println(a==null); Thread.sleep(100); } } } |
|
返回顶楼 | |