http://www.blogcn.com/user8/flier_lu/index.html?id=1620823&run=.0C124A1
OFBiz项目的主要人员之一David Jones在一篇讨论中,Re: double-checked synch, Entity Engine & Struts,讨论了为什么不在OFBiz中使用Struts和Double Checked Locking模式。
Double Checked Locking 模式是一种非常有效的对 Singleton 模式的优化方法。
一般来说,Singleton模式可以通过如下代码简单实现:
而为了支持多线程时可能存在的并发访问,需要进行锁定保护
这样的锁定实际上只在第一次访问getHelper函数并初始化Helper对象这个时间段有效,其他时间都是冗余的。因此在C++/C#等语言中大量使用了 Double-Checked Locking 模式进行优化,如
具体的模式原理和分析,可以参考关于ACE中此模式使用的文章。
ACE中的Double Checked Locking 模式
但在 Java 中,这种使用方法实际上是错误的
The "Double-Checked Locking is Broken" Declaration
这是因为 Java 语言规范和 C++/C# 等不同,是一个非常灵活的规范。Java 编译器可以自由地重排变量的初始化和访问顺序,以提高运行时效率;同时 Java 中的变量访问是可以被自动缓存到寄存器的,这也导致潜在的 JIT 编译器相关的依赖性错误。每个编译器可能有不同的实现方法,同样的代码在不同编译器和执行环境下可能有不同的表现,呵呵
关于 Java 基于编译器的变量重排和内存模型的相关知识可以参考
Synchronization and the Java Memory Model
就目前来说,一种可行的解决办法是使用线程局部存储来保存 Singleton 实例,避免 JIT 对其进行优化
而要彻底解决这个问题,还是得等 JSR 133 提供的volatile 关键字,用于修饰变量的可变性,强制屏蔽编译器和 JIT 的优化工作。
更详尽的资料可以参考 The Java Memory Model
OFBiz项目的主要人员之一David Jones在一篇讨论中,Re: double-checked synch, Entity Engine & Struts,讨论了为什么不在OFBiz中使用Struts和Double Checked Locking模式。
Double Checked Locking 模式是一种非常有效的对 Singleton 模式的优化方法。
一般来说,Singleton模式可以通过如下代码简单实现:
以下为引用:
// Single threaded version
class Foo
{
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}
而为了支持多线程时可能存在的并发访问,需要进行锁定保护
以下为引用:
// Correct multithreaded version
class Foo
{
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}
这样的锁定实际上只在第一次访问getHelper函数并初始化Helper对象这个时间段有效,其他时间都是冗余的。因此在C++/C#等语言中大量使用了 Double-Checked Locking 模式进行优化,如
以下为引用:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo
{
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
具体的模式原理和分析,可以参考关于ACE中此模式使用的文章。
ACE中的Double Checked Locking 模式
但在 Java 中,这种使用方法实际上是错误的
The "Double-Checked Locking is Broken" Declaration
这是因为 Java 语言规范和 C++/C# 等不同,是一个非常灵活的规范。Java 编译器可以自由地重排变量的初始化和访问顺序,以提高运行时效率;同时 Java 中的变量访问是可以被自动缓存到寄存器的,这也导致潜在的 JIT 编译器相关的依赖性错误。每个编译器可能有不同的实现方法,同样的代码在不同编译器和执行环境下可能有不同的表现,呵呵
关于 Java 基于编译器的变量重排和内存模型的相关知识可以参考
Synchronization and the Java Memory Model
就目前来说,一种可行的解决办法是使用线程局部存储来保存 Singleton 实例,避免 JIT 对其进行优化
以下为引用:
class Foo
{
/** If perThreadInstance.get() returns a non-null value, this thread
has done synchronization needed to see initialization of helper */private final ThreadLocal perThreadInstance = new ThreadLocal();
private Helper helper = null;
public Helper getHelper()
{
if (perThreadInstance.get() == null) createHelper();
return helper;
}
private final void createHelper()
{
synchronized(this)
{
if (helper == null)
helper = new Helper();
}
// Any non-null value would do as the argument here
perThreadInstance.set(perThreadInstance);
}
}
而要彻底解决这个问题,还是得等 JSR 133 提供的volatile 关键字,用于修饰变量的可变性,强制屏蔽编译器和 JIT 的优化工作。
以下为引用:
// Works with acquire/release semantics for volatile
// Broken under current semantics for volatile
class Foo
{
private volatile Helper helper = null;
public Helper getHelper()
{
if (helper == null)
{
synchronized(this)
{
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}
更详尽的资料可以参考 The Java Memory Model