如果初始化程序代码在方法之间拆分,双重检查锁习惯用法是否安全?
Is the double-checked lock idiom safe if the initializer code is split between methods?
我在一些开源代码中发现了这段代码,我发现它的行为不稳定,应该重命名为 unnamed(名称已更改)。我很确定它不是线程安全的,因为资源缺少 volatile 关键字。
此外,我不确定按原样拆分时该成语是否有效。 Josh Bloch claims 这个成语很脆弱,应该按照文档来实现,否则它可能不起作用(尽管他没有解释原因)。加上双写不闻写。
除了此处缺少 volatile 之外,此代码在多线程应用程序中如何失败?
private Resource defaultResource = null;
public Resource getSession() {
if (defaultResource == null) {
defaultResource = initializeResource();
}
return defaultResource;
}
private synchronized Resource initializeResource() {
if (defaultResource == null) {
defaultResource = getResourceBuilder().build();
}
return defaultResource;
}
编辑:使用 Java 8
不,这不安全 -- 但由于通常的双重检查锁问题,而不是因为第二次方法调用。具体来说,第二个线程可以看到一个非空 defaultResource
并因此在没有看到同步方法的情况下访问它,因此不会在第一个线程上调用它时建立先行边;但这与内联 synchronized (this)
块的问题完全相同。
关于易失性,您也说对了。如果该字段是易变的,那么读取它会在其之前的写入中建立一个 happens-before。
方法与 Java 内存模型 (JMM) 无关 — 除非它们是同步的,在这种情况下它们的效果与 synchronized
块完全相同。我鼓励您考虑具体的 操作 ,而不是考虑方法:哪些操作建立先行发生边缘?在您提供的代码中,唯一可以提供先行发生边缘的操作是在同一监视器的先前释放之后(通过退出该监视器的另一个线程)获取 this
的监视器(通过进入同步方法)方法)。那么你不得不问:有没有什么方法可以在不执行这些操作的情况下读取非空 defaultResource
?答案是肯定的:事实上,只要 defaultResource
在 getSession
的开头非空,它就会完成。因此,这些访问中的每一个都是数据竞争。
我在一些开源代码中发现了这段代码,我发现它的行为不稳定,应该重命名为 unnamed(名称已更改)。我很确定它不是线程安全的,因为资源缺少 volatile 关键字。
此外,我不确定按原样拆分时该成语是否有效。 Josh Bloch claims 这个成语很脆弱,应该按照文档来实现,否则它可能不起作用(尽管他没有解释原因)。加上双写不闻写。
除了此处缺少 volatile 之外,此代码在多线程应用程序中如何失败?
private Resource defaultResource = null;
public Resource getSession() {
if (defaultResource == null) {
defaultResource = initializeResource();
}
return defaultResource;
}
private synchronized Resource initializeResource() {
if (defaultResource == null) {
defaultResource = getResourceBuilder().build();
}
return defaultResource;
}
编辑:使用 Java 8
不,这不安全 -- 但由于通常的双重检查锁问题,而不是因为第二次方法调用。具体来说,第二个线程可以看到一个非空 defaultResource
并因此在没有看到同步方法的情况下访问它,因此不会在第一个线程上调用它时建立先行边;但这与内联 synchronized (this)
块的问题完全相同。
关于易失性,您也说对了。如果该字段是易变的,那么读取它会在其之前的写入中建立一个 happens-before。
方法与 Java 内存模型 (JMM) 无关 — 除非它们是同步的,在这种情况下它们的效果与 synchronized
块完全相同。我鼓励您考虑具体的 操作 ,而不是考虑方法:哪些操作建立先行发生边缘?在您提供的代码中,唯一可以提供先行发生边缘的操作是在同一监视器的先前释放之后(通过退出该监视器的另一个线程)获取 this
的监视器(通过进入同步方法)方法)。那么你不得不问:有没有什么方法可以在不执行这些操作的情况下读取非空 defaultResource
?答案是肯定的:事实上,只要 defaultResource
在 getSession
的开头非空,它就会完成。因此,这些访问中的每一个都是数据竞争。