我有专门用于延迟初始化和存储对象的类,创建不是必需的线程安全。这是代码:
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
。这在测试中有效,但我不能说它是正确的,并且不会被编译器优化。
是否可以使用此appraoch或object
字段必须声明为volatile
?
另外,包装类的变体被认为是行
final Object tmpRef = createObject();
必须替换为这个:
Object tmpRef = new FinalWrapper(createObject()).getVal();
Wrapper类看起来像这样:
private class FinalWrapper {
private final Object val;
public FinalWrapper(Object val) {
this.val = val;
}
public Object getVal() {
return val;
}
}
是否可以在多线程环境中安全地使用这些示例(尤其是具有最终本地字段的变体)?
答案 0 :(得分:2)
object = NOT_INITIALIZED;
如果你认为这是一个技巧,可以避免通常懒惰的单身人士的问题,你只需要
object = null;
那么它是不正确的;你的伎俩没有赢得任何线程安全。你不能用volatile
变量指向懒惰的初始化对象来击败标准的双重检查成语。所以我的建议是摆脱额外的复杂性,使用null
,然后使用volatile
。
回答你的意见:
JMM保证只用最终字段进行calss初始化始终是线程安全的。
无论字段类型如何,类初始化总是线程安全。每个类的使用都保证看到静态字段引用的对象至少与所有类初始化代码完成时一样是最新的。
是否适用于本地最终字段?
通过解除引用最终字段到达的对象将至少与包含最终字段的对象的构造函数完成时的最新对象一样。但是,在您的解决方案中,您甚至不会取消引用该字段,只需检查其值即可。对于null
常量的值,它等同于检查NOT_INITIALIZED
的相等性。
答案 1 :(得分:1)
您应该将object
变量标记为volatile
至guarantee thread safety,同时请注意此模式仅在Java 1.5 and later中安全。
引用Joshua Bloch时,这是一段棘手的代码:
成语非常快,但又复杂而细腻,所以不要 试图以任何方式修改它。只需复制和粘贴 - 通常不是 好主意,但在这里适当