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

时间:2011-05-12 09:45:43

标签: 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;
    }
}

有两个方面需要注意:

  1. getInstance() 已同步,因此在初始化INSTANCE后无需同步费用
  2. createInstance()已同步
  3. 所以,问题是:这段代码有什么问题吗?它是合法的吗?总是线程安全吗?

5 个答案:

答案 0 :(得分:7)

您需要将INSTANCE声明为volatile才能生效:

private static volatile DBAccessService INSTANCE;

请注意,它仅适用于Java 5及更高版本。请参阅The "Double-Checked Locking is Broken" Declaration

答案 1 :(得分:4)

为了解决这个特殊问题Java concurrency in practice(由基本上编写java.util.concurrent库的团队编写)推荐Lazy Initialization holder class idiom(我的副本中的第348页,清单16.6,而不是16.7)< / p>

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

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

与他们所说的不稳定问题有关:

  

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

答案 2 :(得分:2)

In this article声称如果您使用单独的Singleton类,“双重检查日志记录”不是问题:

public class DBAccessHelperSingleton {
    public static DBAccessHelper instance = new DBAccessHelper(); 
} 

它具有相同的优点:该字段在第一次引用之前未实例化。

如前所述,如果您希望保持原样volatile,那么只要您定位JDK&gt; = 5.

答案 3 :(得分:1)

这不是线程安全的,请检查一个优秀的article。无法保证一个线程将看到完全初始化的DsAccessService实例。为什么不使用简单的

public class DBAccessService() {
     public static DBAccessService INSTANCE = new DBAccessService();
}

答案 4 :(得分:0)

看起来很好。 如果两个线程调用getInstance()并且INSTANCE未初始化,则只有一个线程能够继续使用createInstance(),第二个线程将看到该实例不为空。

唯一的问题是INSTANCE的volatile关键字。否则java可以缓存它。