Is static initialization of non-final static fields safe?

Viewed 257

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 (i.e. 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?

1 Answers
Related