是否保证每个线程都会看到非最终字段的实例初始值设定项的值(字段的等号的表达式)?例如:
class Foo {
private boolean initialized = false; // Initializer
private final Lock lock = new ReentrantLock();
public void initialize() {
lock.lock()
try {
// Is initialized always false for the first call of initialize()?
if (initialized) {
throw new IllegalStateException("already initialized");
}
// ...
initialized = true;
} finally {
lock.unlock();
}
}
}
答案 0 :(得分:4)
在特定情况下您没问题,因为false
也是boolean
字段的默认值。如果你的实例变量初始化是:
private boolean initialized = true;
然后你无法保证线程会读true
。
请注意,如果该字段是静态的,那么由于类加载语义,您将获得这样的保证。
参考:JLS 17.4.4(强调我的)
对每个变量写入默认值(零,false或null)与每个线程中的第一个操作同步。
虽然在分配包含变量的对象之前将默认值写入变量似乎有点奇怪,但概念上每个对象都是在程序开头创建的,其默认初始值为 的
答案 1 :(得分:0)
同样代表初始化器,对于引用字段也是如此:
如果您希望其他线程看到其当前值,则必须使用volatile
。
volatile
并不确定:大多数情况下你必须使用synchronized
或其他同步手段才能确定,但在这种情况下volatile
就足够了。
请参阅this有关volatile
用法和线程安全的问题。
另一件事是只有一个线程可以构造一个对象,并且在构造对象时发生实例初始化。但是你必须小心谨慎,不要让this
引用从构造函数中逃脱。
答案 2 :(得分:0)
在我看来,你正在寻找的是一种线程安全的延迟初始化方式。由于直接使用诸如ReentrantLock
之类的低级类可能很难正确完成,我建议使用双重检查成语:
private volatile FieldType field = null; // volatile!
public FieldType getField() {
FieldType result = field; // read volatile field only once, after init
if (result == null) {
synchronized(this) {
result = field;
if (result == null) {
result = computeFieldValue();
field = result;
}
}
}
return result;
}
请注意,Double-Check锁定至少需要Java 1.5。在旧版本的Java上,它已被破坏。
答案 3 :(得分:0)
除非您有其他一些保护措施,例如锁定或同步块,否则无法保证单独使用非最终字段。即在这种情况下,由于使用该值的方式,它总是正确的。
BTW:为简单起见,我建议您始终构建代码,以便在构造函数中初始化组件。这样可以避免检查对象未初始化或初始化两次等问题。