问题请参考:您能看出这个Double Check里的问题吗?

已经很有很多朋友得到了结果,是由于m_categories过早初始化,而导致double check的验证条件被破坏(或者说,满足)。

private object m_mutex = new object();
private Dictionary<int, Category> m_categories;public Category GetCategory(int id)
{if (this.m_categories == null){lock (this.m_mutex){if (this.m_categories == null){LoadCategories();}}}return this.m_categories[id];
}private void LoadCategories()
{this.m_categories = new Dictionary<int,Category>();this.Fill(GetCategoryRoots());
}private void Fill(IEnumerable<Category> categories)
{foreach (var cat in categories){this.m_categories.Add(cat.CategoryID, cat);Fill(cat.Children);}
}

假设第一个线程进入了GetCategory方法,它自然可以畅通无阻地执行LoadCategories。只可惜,在LoadCategories方法的第一行就为m_categories设置了一个空字典。如果现在立即有另一个线程访问了GetCategory方法,就会发现m_categories字段不是null,并直接执行this.m_categories[id]这行代码——但此时,第一个线程还没有将这个字典填充完毕!

因此,这段代码其实是一个有问题的Double Check实现。那么我们该怎么改呢?

一位匿名朋友提出,可以增加一个标记,用来表示有没有初始化完毕。如下:

private bool m_initialized = false;public Category GetCategory(int id)
{if (!this.m_initialized){lock (this.m_mutex){if (!this.m_initialized){LoadCategories();this.m_initialized = true;}}}return this.m_categories[id];
}

这是个非常漂亮的做法,完全没有问题。不过我并没有使用这种修改方式。

private void LoadCategories()
{var categories = new Dictionary<int,Category>();Fill(categories, GetCategoryRoots());this.m_categories = categories;
}private static void Fill(Dictionary<int, Category> container, IEnumerable<Category> categories)
{foreach (var cat in categories){container.Add(cat.CategoryID, cat);Fill(container, cat.Children);}
}

我稍稍改变了一下Fill方法,它不再直接访问m_categories字段,而是把内容填充至container参数中。而在LoadCategories方法中,我们创建一个字典,但是直到填充完毕后才将其赋给m_categories字段。这样就保证了在m_categories字段不为null的时候,一定已经初始化完毕了。这也是一种可行的办法。我没有使用第一种做法的原因,并不是因为所谓的“节省空间”,而是……一下子就想到了第二种做法。:)

这里反映了Double Check在使用时的一个准则:在满足if条件的时候,一定要确保所有的初始化已经完成了。或者说,一定要将“满足if条件”的操作放在初始化完毕之后进行。至于是否使用某个标记,倒不是什么大问题。

如果您使用.NET编写代码,目前已经没有问题了,但是在某些情况下这样的代码还是会出现问题。我认为这也是多线程编程时最麻烦的地方——就是所谓的“Memory Consistency Model”。

为了性能考虑,编译器在将文本代码转化为机器码,以及CPU在执行机器码时都会对执行进行“重新排序(reorder)”,reorder的作用是为了提升性能。虽然从单线程的角度来看,reorder不会形成问题,但是在多线程的环境中,reorder就会破坏代码的逻辑了。如果没有一个“标准”在进行统一的话,不同的编译器,虚拟机,CPU架构都会有不同的reorder策略。例如微软并行库之父Joe Duffy在这篇文章中简单地提到了不同平台(JVM / CLR 2.0)或不同CPU架构(x86 / IA64)下reorder规则的区分。

而臭名昭著的Double Check的bug便是由于store reordering造成的。在JVM或普通的C、C++中并不保证store reordering不会发生。也就是说,您在代码中看到的两个变量的“设置”顺序,并不代表CPU在执行的时候,也是同样的效果。因此,如果你观察下面的代码:

class Foo { private Helper helper = null;public Helper getHelper() {if (helper == null) synchronized(this) {if (helper == null) helper = new Helper();}return helper;}
}

看上去这是一段再正常不过的实现Double Check的Java代码,但是由于发生了store reordering,可能在Helper构造函数中的操作还没有全部执行完成之前,就设置了helper字段。因此另一个线程就可能会访问到一个没有初始化完整的Helper对象。如果您对这个话题感兴趣,可以参考《The "Double-Checked Locking is Broken" Declaration》。

而在CLR 2.0中,只会发生load reordering,而不会出现store reordering。于是.NET中编写的Double Check代码不会出现任何问题。那么CLR是如何保证在不同的CPU平台上出现相同的行为呢?那是因为CLR会根据不同的平台,在合适的情况下插入一些辅助代码(如Memory Barrier),可见CLR为我们的并行编程环境已经形成了一个相对比较方便的平台了——虽然,并行编程还是很困难。

(似乎关于Memory Model的有些说法不太确切,随时更新,希望了解这些的朋友们也可以提点意见,我晚上回家后再查些资料)

from: http://blog.zhaojie.me/2009/09/double-check-failure-answer.html

您能看出这个Double Check里的问题吗?(解答)相关推荐

  1. 您能看出这个Double Check里的问题吗?

    昨天在做code review时看到一位同事写了这样的代码.这段代码的目的使用Double Check的做法来保证线程安全的延迟加载.但是我看到这代码之后发现了一个问题,这个问题不是第一次出现.因此, ...

  2. 为什么单例模式中的Double Check要加volatile

    对于单例模式的详细内容,请参考我的上一篇文章 https://blog.csdn.net/Jarvenman/article/details/100136562 在单例模式中,有一种写法叫Double ...

  3. Double Check形式的单例模式

    这两天在看开源项目时,发现Event Bus和Universalimageloader中写单例模式都是Double Check的形式.平时总是看到各种各样的单例模式,如,饿汉式,懒汉式等等.其中大多存 ...

  4. Eureka源码-double check单例模式运用

    1.在看源码之前,首先先解释一下什么是double check,以及单例模式中为什么需要double check来进行单例模式的创建? double check,也叫双重检测,主要利用两次的判断进行校 ...

  5. double check java_由java double check说起

    引子 在java中,为了保证某种资源只被初始化一次,我们通常会将其放入同步代码块中,如: public synchronized Resource getResource(){ if (resourc ...

  6. 关于Installshield里一些常见问题的解答—艾泽拉斯之海洋女神出品

    原文:关于Installshield里一些常见问题的解答-艾泽拉斯之海洋女神出品 上一篇:一个完整的安装程序实例-艾泽拉斯之海洋女神出品(五) --补遗 转载时请务必保留转载出处和由艾泽拉斯之海洋女神 ...

  7. 为什么单例模式需要double check

    2019独角兽企业重金招聘Python工程师标准>>> 最近被多线程问题(multi-thread issue)弄昏了头.以前虽然也知道系统里要考虑多线程问题,也无数次见到doubl ...

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

    首先这两种方式都是延迟初始化机制,就是当要用到的时候再去初始化. 但是Effective Java书中说过:除非绝对必要,否则就不要这么做. 1. DCL (double checked lockin ...

  9. 单例模式之双重检查锁(double check locking)的发展历程

    不安全的单例 没有注意过多线程安全问题的时候,我们的单例可能是这样的: public final class Singleton {private static Singleton instance; ...

最新文章

  1. NVIDIA GPUs上深度学习推荐模型的优化
  2. 两个数组的交集 II
  3. R语言gganimate包创建可视化gif动图:gganimate包创建动态线型图动画基于transition_time函数、使用geom_point函数显示动画移动的数据点、并保留线图中的全部数据点
  4. 天生一对Maven2+Jetty -- Maven2创建并管理WebApp,并使用Maven Jetty Plugin在Eclipse中调试...
  5. 逻辑运算符和||与(和|)的区别
  6. 2021-03-08 Halcon初学者知识 【20】如何实现矩形拟合
  7. Winform中实现右下角Popuo弹窗提醒效果(附代码下载)
  8. Java面试宝典之开源框架!
  9. php sodium 加密解密,sodium库(加密)
  10. (libgdx学习)InputProcessor InputMultiplexer
  11. python mysql 数据类型_mysql学习:mysql数据类型有哪些?
  12. 问题三:类的头文件和实现文件分别写什么(用向量表示RGB输出“第一张图片”)
  13. php记录上次观看记录,PHP Cookei记录用户历史浏览信息的代码
  14. stackoverflow图片大小修改
  15. vue2.x 微信公众号授权拿取code,静默登录
  16. 使用计算机眼睛保护方法有哪些,​计算机族的“护眼诀窍”
  17. http://dongxicheng.org/
  18. 浅谈如何在优麒麟22.04中使用Eigenstrat和Plink工具生成类23andMe格式原始数据
  19. JAVA用爬山法解决八皇后问题_八皇后问题爬山法实现(C语言)
  20. 城镇水务系统碳减排路径|给水与再生水系统

热门文章

  1. TalkingData大规模机器学习的应用
  2. 小工匠聊架构-超高并发秒杀系统设计 02_数据的动静分离
  3. Spring5源码 - 构建源码环境
  4. JVM - 剖析Java对象头Object Header之对象大小
  5. Spring Boot2.x-10 基于Spring Boot 2.1.2 + Mybatis 2.0.0实现多数据源,支持事务
  6. Spring Boot2.x-06Spring Boot基础-使用@Conditional注解根据特定的条件装配bean
  7. Android Studio(IDEA)太占系统盘?帮你移动下
  8. Intellij IDEA(Android Studio)好用的插件和软件
  9. 线程池 c linux 编程,关于c++:linux-c编程之高效线程池如何实现无琐化
  10. ios 获取沙河文件夹_关于 iOS 沙盒的目录结构和获取