仔细检查最终变量是否正常工作

Is double check with final variable working

我有 class 设计用于延迟初始化和存储创建不需要线程安全的对象。这是代码:

class SyncTest {
    private static final Object NOT_INITIALIZED = new Object();
    private Object object;

    /**
     * It's guaranteed by outer code that creation of this object is thread safe
     * */
    public SyncTest() {
       object = NOT_INITIALIZED;
    }

    public Object getObject() {
        if (object == NOT_INITIALIZED) {
            synchronized (NOT_INITIALIZED) {
                if (object == NOT_INITIALIZED) {
                    final Object tmpRef = createObject();
                    object = tmpRef;
                }
            }
        }
        return object;
    }

    /**
     * Creates some object which initialization is not thread safe
     * @return required object or NOT_INITIALIZED
     * */
    private Object createObject() {
        //do some work here
    }
}

这里 final 变量 tmpRef 用于在将创建的对象分配给检查变量 object 之前存储创建的对象。这在测试中有效,但我不能肯定地说它是正确的并且不会被编译器优化。 可以使用此方法还是必须将 object 字段声明为 volatile?

还考虑了带有包装器 class 的变体,其中行

final Object tmpRef = createObject();

必须换成这个:

Object tmpRef = new FinalWrapper(createObject()).getVal();

包装器 class 看起来像这样:

private class FinalWrapper {
    private final Object val;

    public FinalWrapper(Object val) {
        this.val = val;
    }

    public Object getVal() {
        return val;
    }
}

这些示例中的一些能否在多线程环境中安全使用(尤其是具有 final 本地字段的变体)?

您应该将 object 变量标记为 volatileguarantee thread safety, also note that this pattern is only safe in Java 1.5 and later

引用 Joshua Bloch 的话,这是一段棘手的代码:

The idiom is very fast but also complicated and delicate, so don't be tempted to modify it in any way. Just copy and paste -- normally not a good idea, but appropriate here

object = NOT_INITIALIZED;

如果您将此设想为一种技巧,可以避免通常的懒惰单身人士的问题,而您只需

object = null;

那么不正确;你的把戏并没有为你赢得任何线程安全。您无法通过 volatile 变量指向延迟初始化的对象来击败标准的双重检查习语。所以我的建议是摆脱额外的复杂性,使用 null,并使用 volatile.

正在回答您的评论:

It's guranteed by JMM that calss initialization with only final fields is always threadsafe.

Class 初始化是 总是 线程安全的,无论字段的种类如何。 class 的每次使用都保证看到静态字段引用的对象至少与所有 class 初始化代码完成时一样最新。

Is the same applicable for local final fields?

通过取消引用 final 字段获得的对象至少与包含 final 字段的对象的构造函数完成时一样是最新的。但是,在您的解决方案中,您甚至从未取消引用该字段,您只是检查它的值。对于 NOT_INITIALIZED 常量的值,它严格等同于检查 null 是否相等。