此代码是否解决了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;
}
}
有两个方面需要注意:
getInstance()
不已同步,因此在初始化INSTANCE后无需同步费用createInstance()
已同步所以,问题是:这段代码有什么问题吗?它是合法的吗?总是线程安全吗?
答案 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可以缓存它。