非最终初始化程序是否安全?

时间:2013-06-07 09:17:08

标签: java multithreading

是否保证每个线程都会看到非最终字段的实例初始值设定项的值(字段的等号的表达式)?例如:

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();
    }
  }
}

4 个答案:

答案 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:为简单起见,我建议您始终构建代码,以便在构造函数中初始化组件。这样可以避免检查对象未初始化或初始化两次等问题。