简体   繁体   中英

Are non-final initializers threadsafe?

It is guranteed or not that every thread sees the value of an instance initializer (the expression right to the equal sign of a field) for a non-final field? For example:

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

In that specific case you are fine because false is also the default value for boolean fields . If your instance variable initialisation were:

private boolean initialized = true;

Then you would have no guarantee that a thread would read true .

Note that if the field were static, you would have such a guarantee due to class loading semantics.

Reference: JLS 17.4.4 (emphasis mine)

The write of the default value (zero, false, or null) to each variable synchronizes-with the first action in every thread.
Although it may seem a little strange to write a default value to a variable before the object containing the variable is allocated, conceptually every object is created at the start of the program with its default initialized values .

The same stands for initialzers that is true for referencing fields:

If you want other threads to see its current value you have to use volatile .

volatile is not surefire however: most of the case you have to use synchronized or other means of synchronizing in order to be sure but in this case a volatile will be enough.

Please refer to this question about the usage of volatile and about thread safety.

Another thing is that only one thread can construct an object and the instance initialization is happening when the object is being constructed. You have to taker care however not to let the this reference escape from the constructor.

It seems to me that what you are looking for is a thread-safe way of lazy initialization. Since direct use of low-level classes such as ReentrantLock can be quite hard to do correctly, I would instead recommend the double-check idiom:

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

Note that Double-Check locking requires at least Java 1.5. On older versions of Java it was broken.

A non-final field alone is not guaranteed to be correctly seen, unless you have some other protection such as a lock or synchronized block. ie in this case it will always be correct due to the way the value is used.

BTW: For simplicity reasons, I suggest you always structure your code so the component is initialized in the constructor. This avoids such issues as checking the objects is not initialised or initialised twice.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM