此代码是否解决了Java中的双重检查锁定问题?

aka*_*lou 5 java singleton thread-safety double-checked-locking

此代码是否解决了Java中的双重检查锁定问题?

public class DBAccessService() {
    private static DBAccessService INSTANCE;  

    private DBAccessService() {}

    public static DBAccessService getInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        return createInstance();
    }

    private static synchronized DBAccessService createInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        DBAccessService instance = new DBAccessService();
        INSTANCE = instance;

        return INSTANCE;
    }
}
Run Code Online (Sandbox Code Playgroud)

有两个方面需要注意:

  1. getInstance()同步的,所以INSTANCE被初始化之后没有用于同步没有成本
  2. createInstance() 是同步的

所以,问题是:这段代码有什么问题吗?它合法且始终是线程安全的吗?

Whi*_*g34 7

需要声明INSTANCEvolatile,它的工作:

private static volatile DBAccessService INSTANCE;
Run Code Online (Sandbox Code Playgroud)

请注意,它仅适用于Java 5及更高版本.请参阅"双重检查锁定已损坏"声明.


Nic*_*cue 4

为了解决这个特定问题,Java 并发实践(由基本上编写 java.util.concurrent 库的团队编写)推荐使用延迟初始化持有者类习惯用法(我的副本中的第 348 页,清单 16.6,而不是 16.7)

@ThreadSafe
public class DBAccessServiceFactory {
  private static class ResourceHolder {
    public static DBAccessService INSTANCE = new DBAccessService();
  }
  public static DBAccessService getResource() {
    return ResourceHolder.INSTANCE;
  }
}
Run Code Online (Sandbox Code Playgroud)

这始终是合法且线程安全的。我不是专家,所以我不能说这比你的代码更好。然而,考虑到这是 Doug Lea 和 Joshua Bloch 推荐的模式,我总是会在你或我发明的代码上使用它,因为它很容易出错(正如这个问题的错误答案数量所证明的那样) )。

关于不稳定的问题,他们说:

JMM(Java 5.0 及更高版本)中的后续更改使得 DCL 能够在资源变得易失性的情况下工作...但是,惰性初始化持有者习惯用法提供了相同的好处并且更容易理解。

  • @zshamrock:因为简单的 *public static final ... = new ...* 不是延迟实例化的。关键是要延迟实例化**并**避免**所有**同步成本。唯一干净的方法是使用*按需初始化持有者类习惯用法*(如*Effective Java* 中的命名和解释)。请注意,使用 Java 1.5 的 *volatile* 有点违背了整个要点,因为即使您不使用 *synchronize/synchronized* 关键字,您仍然会增加同步成本(*volatile* 的成本也很高)。所以,就是这样:lazy + no-synchro:*按需初始化类习惯用法* :) (2认同)