经典j2ee设计模式Double-Checked
Locking失效问题
双重检查锁定失效问题,一直是JMM无法避免的缺陷之一.了解DCL失效问题,
可以帮助我们深入JMM运行原理.
要展示DCL失效问题,
首先要理解一个重要概念- 延迟加载(lazy loading).
非单例的单线程延迟加载示例:
class Foo
{
private Resource res = null;
public Resource getResource()
{
//
普通的延迟加载
if (res == null)
res = new Resource();
return res;
}
}
非单例的 多线程延迟加载示例:
Class Foo
{
Private Resource res = null;
Public
synchronized
Resource getResource()
{
//
获取实例操作使用同步方式
,
性能不高
If
(res == null) res = new Resource();
return
res;
}
}
非单例的 DCL多线程延迟加载示例:
Class Foo
{
Private
Resource res = null;
Public
Resource getResource()
{
If
(res == null)
{
//
只有在第一次初始化时
,
才使用同步方式
.
synchronized(this)
{
if(res
== null)
{
res
= new Resource();
}
}
}
return
res;
}
}
Double-Checked
Locking看起来是非常完美的。但是很遗憾,根据Java的语言规范,上面的代码是不可靠的。
出现上述问题, 最重要的2个原因如下:
1, 编译器优化了程序指令,
以加快cpu处理速度.
2, 多核cpu动态调整指令顺序,
以加快并行运算能力.
问题出现的顺序:
1, 线程A, 发现对象未实例化,
准备开始实例化
2, 由于编译器优化了程序指令,
允许对象在构造函数未调用完前, 将 共享变量的引用指向 部分构造的对象, 虽然对象未完全实例化, 但已经不为null了.
3, 线程B,
发现部分构造的对象已不是null, 则直接返回了该对象.
不过, 一些著名的开源框架,
包括jive,lenya等也都在使用DCL模式, 且未见一些极端异常.
说明,
DCL失效问题的出现率还是比较低的.
接下来就是性能与稳定之间的选择了?
DCL的替代 Initialize-On-Demand
:
public class Foo {
//
似有静态内部类
,
只有当有引用时
,
该类才会被装载
private static class LazyFoo {
public static Foo foo = new Foo();
}
public static Foo getInstance() {
return LazyFoo.foo;
}
}
维基百科的DCL解释:
DCL的完美解决方案: