双重检查锁定被广泛引用并用作在多线程环境中实现延迟初始化的有效方法。
不幸的是,当用 Java 实现时,如果没有额外的同步,它就不能以独立于平台的方式可靠地工作。当用其他语言(例如 C++)实现时,它取决于处理器的内存模型、编译器执行的重新排序以及编译器和同步库之间的交互。由于这些都没有在 C++ 等语言中指定,因此很难说它可以在什么情况下工作。显式内存屏障可用于使其在 C++ 中工作,但这些屏障在 Java 中不可用。
双重检查锁定是在进入同步块之前和之后检查惰性初始化对象的状态,以确定是否初始化对象的做法。
如果没有对float或int以外的任何可变实例的额外同步,它就不能以独立于平台的方式可靠地工作。对任何其他类型的基元或可变对象的延迟初始化使用双重检查锁定,可能会导致第二个线程在第一个线程仍在创建时使用未初始化或部分初始化的成员,并使程序崩溃。
有多种方法可以解决此问题。最简单的方法是根本不使用双重检查锁定,而是同步整个方法。对于JVM的早期版本,出于性能原因,通常建议不要同步整个方法。但是,在较新的JVM中,同步性能已经提高了很多,所以这是现在的首选解决方案。如果您希望避免完全使用synchronized,则可以使用内部静态类来保存引用。内部静态类保证延迟加载。
不安全的编码示例:
不幸的是,该代码在存在优化编译器或共享内存多处理器的情况下不起作用。
@NotThreadSafe
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
static class Resource {
}
}
解决方法示例:
1、
@ThreadSafe
public class SafeLazyInitialization {
private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
static class Resource {
}
}
2、
@ThreadSafe
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource(); // This will be lazily initialised
}
public static Resource getResource() {
return ResourceFactory.ResourceHolder.resource;
}
static class Resource {
}
}
3、
class ResourceFactory {
private volatile Resource resource;
public Resource getResource() {
Resource localResource = resource;
if (localResource == null) {
synchronized (this) {
localResource = resource;
if (localResource == null) {
resource = localResource = new Resource();
}
}
}
return localResource;
}
static class Resource {
}
}