Multithreading 这是双重检查锁定的安全版本吗?

Multithreading 这是双重检查锁定的安全版本吗?,multithreading,language-agnostic,locking,atomic,Multithreading,Language Agnostic,Locking,Atomic,Wikipedia的canonical Breaked double Check locking的稍微修改版本: class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (helper == null) {

Wikipedia的canonical Breaked double Check locking的稍微修改版本:

class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {

                    // Create new Helper instance and store reference on
                    // stack so other threads can't see it.
                    Helper myHelper = new Helper();

                    // Atomically publish this instance.
                    atomicSet(helper, myHelper);
                }
            }
        }
        return helper;
    }
}
假设底层的atomic ops库工作正常,简单地发布新创建的Helper实例是否会使这个双重检查锁定习惯用法安全?我意识到,在Java中,可以只使用
volatile
,但即使示例是在伪Java中,这应该是一个语言不可知的问题

另请参见:


我认为,如果不完全脱离代码,你就无法以一种与语言无关的方式回答这个问题。这完全取决于
synchronized
atomicSet
如何在伪代码中工作。

我认为,如果不完全脱离代码,你无法以语言无关的方式回答这个问题。这完全取决于
synchronized
atomicSet
如何在伪代码中工作。

这完全取决于平台/语言的确切内存模型


我的经验法则是:不要这样做。无锁(在这种情况下是减少锁)编程很难,除非你是线程忍者,否则不应该尝试。只有当你有了分析证据证明你真的需要它时,你才应该考虑它,在这种情况下,你会得到关于该特定平台线程的绝对最好和最新的书,看看它是否能帮助你。

这完全取决于你的平台/语言的确切内存模型


我的经验法则是:不要这样做。无锁(在这种情况下是减少锁)编程很难,除非你是线程忍者,否则不应该尝试。只有当你有证据证明你真的需要它时,你才应该考虑它,在这种情况下,您将获得关于该特定平台的线程的绝对最佳和最新的书籍,看看它是否能帮助您。

使用volatile不会阻止多个实例化-但是使用synchronize将阻止创建多个实例。但是,在代码中,可能会在设置帮助程序之前返回帮助程序(线程“A”实例化它,但在设置线程“B”出现之前,帮助程序是非空的,因此会立即返回它。若要解决此问题,请删除第一个if(helper==null).

使用volatile不会阻止多个实例化-但是使用synchronize会阻止创建多个实例。但是,对于代码,可能会在设置帮助程序之前返回帮助程序(线程“A”实例化了它,但在它被设置之前,线程“B”出现,helper是非null的,因此会立即返回它。要解决这个问题,请删除第一个if(helper==null)。

答案取决于语言-它取决于
atomicSet()
提供的保证

如果myHelper的构造可以在
atomicSet()
之后展开,那么变量如何分配到共享状态并不重要

i、 e


如果语言允许这样做,则双重检查将不起作用。

答案取决于语言-归结为
atomicSet()
提供的保证

如果myHelper的构造可以在
atomicSet()
之后展开,那么变量如何分配到共享状态并不重要

i、 e


如果语言允许这样做,那么双重检查将不起作用。

最有可能的是它被破坏了,因为部分构造对象的问题没有得到解决。

最有可能的是它被破坏了,因为部分构造对象的问题没有得到解决。

所有担心部分构造对象的人结构化对象:

据我所知,部分构造对象的问题只是构造函数中的问题。换句话说,在构造函数中,如果对象引用自身(包括其子类)否则,当构造函数返回时,类就被完全构造

我认为您混淆了部分构造与编译器如何优化写入的不同问题。编译器可以选择A)为新的Helper对象分配内存,B)将地址写入myHelper(本地堆栈变量),然后C)调用任何构造函数初始化。在B点之后和C点之前的任何时候,访问myHelper都是一个问题

引用的论文关注的是编译器对写操作的优化,而不是部分构造。在原始的单检查锁解决方案中,优化写入可以允许多个线程看到点B和点C之间的成员变量。此实现通过使用局部堆栈变量避免了写入优化问题

引用论文的主要范围是描述双重检查锁解决方案的各种问题。但是,除非atomicSet方法也与Foo类同步,否则此解决方案不是双重检查锁解决方案。它使用多个锁

我想说这一切都归结于原子赋值函数的实现。该函数需要真正原子化,需要保证处理器本地内存缓存同步,并且需要以比始终同步getHelper方法更低的成本完成所有这些

根据引用的论文,在Java中,它不太可能满足所有这些要求。另外,本文中应该非常清楚的一点是,Java的内存模型经常变化。随着对缓存、垃圾收集等的更好理解的发展,以及适应虚拟机运行的底层真实处理器体系结构的变化,它也随之适应

根据经验,如果您以依赖于底层实现的方式优化Java代码,而不是
// Create new Helper instance and store reference on
// stack so other threads can't see it.
Helper myHelper = new Helper(); // ALLOCATE MEMORY HERE BUT DON'T INITIALISE

// Atomically publish this instance.
atomicSet(helper, myHelper); // ATOMICALLY POINT UNINITIALISED MEMORY from helper

// other thread gets run at this time and tries to use helper object 

// AT THE PROGRAMS LEISURE INITIALISE Helper object.