简体   繁体   中英

Is static initialization of non-final static fields safe?

Consider the following code:

public class Text {
  private static ThreadLocal<CharsetEncoder> encoderFactory =
    new ThreadLocal<CharsetEncoder>() {
      @Override
      protected CharsetEncoder initialValue() {
        return Charset.forName("UTF-8").newEncoder().
            onMalformedInput(CodingErrorAction.REPORT).
            onUnmappableCharacter(CodingErrorAction.REPORT);
      }
    };

  public static ByteBuffer encode(String string, boolean replace)
      throws CharacterCodingException {
    CharsetEncoder encoder = encoderFactory.get();
    ...
  }
}

Can the line that accesses encoderFactory in encode() ever throw a NullPointerException in a concurrent situation?

Yes, I am well aware that in this case encoderFactory could easily be declared as final which will make this question somewhat moot.

However, my interest here is whether the code written as above still safely publishes encoderFactory . If I understand the JLS 12.4 , that should be the case. The steps of static initialization do not seem to leave a possibility that any thread will see static fields in their uninitialized state (ie no happens-before) once it sees the class as initialized. I thought that the JLS makes it reasonably clear that static initialization forms a memory barrier.

Apparently such a NullPointerException has been observed, and we ended up fixing it by making this field final. While it is certainly a good thing to do, I am still puzzled how one can see a null pointer with this pattern, otherwise a much larger problem is afoot, as it could mean that any initial assignment of non-final static fields potentially may not be visible.

If the assumption that the static initialization provides a memory barrier is a reliable one (which I believe it is), I suppose that would necessarily point to a JDK bug then? Can you think of a reason that can happen other than a JDK bug?

It will be thread-safe, as per 12.4.2 :

Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization.

Initialization of a class will be safe in concurrent situations. The fields will be loaded before any thread has a chance to invoke encode , whether the field was declared final or not. The reference type used does not make a difference (including ThreadLocal ).

They go further in explaining the exact steps in which initialization occurs. Threads are not notified until initialization has completed successfully or abruptly (via a thrown exception, resulting in an ExceptionInInitializerError ).

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