1

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

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

包装类的变体也被认为是行

final Object tmpRef = createObject();

必须用这个替换:

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

包装类如下所示:

private class FinalWrapper {
    private final Object val;

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

    public Object getVal() {
        return val;
    }
}

可以在多线程环境中安全地使用其中一些示例(尤其是具有最终本地字段的变体)吗?

4

2 回答 2

2
object = NOT_INITIALIZED;

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

object = null;

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

回答您的意见:

JMM 保证只有最终字段的 calss 初始化始终是线程安全的。

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

是否同样适用于本地最终字段?

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

于 2015-08-17T08:29:27.233 回答
1

您应该将object变量标记为volatile保证线程安全,还要注意这种模式仅在Java 1.5 和更高版本中是安全的。

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

该成语速度很快,但也很复杂和微妙,所以不要试图以任何方式修改它。只需复制和粘贴——通常不是一个好主意,但在这里合适

于 2015-08-17T08:09:24.103 回答