1

在 Wikipedia 中阅读DCL 时,我想知道 DCL 中的问题和建议的解决方案,或者换句话说,为什么volatile需要关键字?简而言之,问题是:在某些情况下,使用 DCL 可能会导致线程使用部分构造的对象。建议的解决方案(对于 JAVA 5+):

// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    helper = result = new Helper();
                }
            }
        }
        return result;
    }

    // other functions and members...
}

现在,我的问题是为什么不放弃volatilefrom 助手?如果我错了,请纠正我,但如果你只是打破这一行:helper = result = new Helper();到:

result = new Helper();
helper = result;

在这种情况下,helper在对象完成之前永远不会获得引用。不是这样吗?表现如何volatile更好?

编辑:假设这段代码:

class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    result = new Helper();
                    result.someMethod();
                    helper = result;
                }
            }
        }
        return result;
    }
}

如果初始化后的下一行不能保证一个完全初始化的对象,我不能调用它的方法。我可以吗?

4

1 回答 1

2

如果省略volatile,编译器可以result完全优化。这个额外的局部变量并没有太大变化。它节省了额外的负载,但它是volatile修复竞争条件的关键字。

假设我们有以下代码片段:

public volatile Object helper;

public synchronized void initHelper() {
    Object result = new Object();
    helper = result;
}

它将被编译为以下伪程序集:

public synchronized void initHelper() {
    Object result = Object.new();
    result.<init>();
    // result is now fully initialized
    // but helper is still null for all threads
    this.helper = result;
    // now all threads can access fully initialized helper
}

如果您删除volatile编译器,则允许假定没有其他线程使用helper并重新排列代码以优化它。它可能决定删除不必要的局部变量并产生以下输出:

public synchronized void initHelper() {
    this.helper = Object.new();
    // from now on other threads can access helper
    this.helper.<init>();
    // but it's fully initialized only after this line
}

如果在initHelper. 编译器永远不会在初始化之前进行此调用。即使在单线程中,这也会改变程序的含义。但是允许进行优化,这将破坏访问该字段的多线程代码。

于 2014-05-25T09:19:22.080 回答