Migarated from here at ‘2012-05-22 17:35:03’.
参考了这里。
DCL是lazy initialization的一种技巧,理论上应该是线程安全的,但是在实际应用中却正相反。
详情请见这里。
前提1:Java 使得它的每个线程使用独立的处理器和一个私有的内存空间,每一个线程的私有内存都会与主存交互与同步。synchronized 块会建立一个内存壁垒(memory barrier) ,当进入synchronized块时会建立一个read barrier :它会使得Thread的本地内存无效,并且从主存中读取所有的变量的值。当退出一个synchronized块时,它会确保将对所有本地内存的改变都同步到主存中。并且synchronized也会保证这段代码是原子操作。
前提2:假设一个new操作需要以下两个步骤(也许更多)1.分配相应内存2.调用构造函数。
前提3:我们不能在书写我们的代码时预计指令真正的执行顺序,他可能由于编译器,处理器和缓存机制的干预而变化。规范规定:编译器编译器,处理器和缓存机制可以任意改变指令顺序,只要不影响结果的值。实例:
1 | public Resource getResource(){ |
解释:
有了以上的前提,我们可以讨论DCL是如何导致线程不安全的。假设:一个线程t1进入getResource()方法,并且执行到b处。这时另一个线程t2进入。当它在a处时t1刚好进行完分配内存的工作,而没有调用构造函数。这时T2会认为resource不为null因而直接来到c处,得到了一个错误的resource实例。显而易见会导致错误。相关参考java language specification chapter 17