非final静态字段的静态初始化是否安全?

3
考虑以下代码:
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();
    ...
  }
}

在并发情况下,encode()中访问encoderFactory的代码行是否会抛出NullPointerException

是的,我很清楚,在这种情况下,将encoderFactory声明为final可以使这个问题变得有点无关紧要。

然而,我的兴趣在于,上述代码是否仍然安全地发布了encoderFactory。如果我理解JLS 12.4,应该是这样的。静态初始化的步骤似乎不会留下任何线程会看到未初始化状态的静态字段(即没有 happens-before),一旦它看到类被初始化。我认为JLS让它相当清楚,静态初始化形成了一个内存屏障。

显然,出现了一个NullPointerException,我们最终通过将此字段设置为final来修复它。虽然这样做肯定是好的,但我仍然感到困惑,如何才能看到这种模式的空指针,否则可能存在更大的问题,因为这可能意味着任何非final静态字段的初始赋值都可能不可见。
如果假设静态初始化提供了内存屏障是可靠的(我相信是这样),那么这必然会指向JDK的错误吗?你能想到除了JDK bug之外的原因吗?
1个回答

1

根据12.4.2,它将支持多线程安全:

由于Java编程语言是多线程的,因此类或接口的初始化需要仔细同步。

在并发情况下,类的初始化将是安全的。无论该字段是否被声明为final,在任何线程有机会调用encode之前,字段都将被加载。使用的引用类型没有区别(包括ThreadLocal)。

他们进一步解释了初始化发生的确切步骤。只有在初始化成功或异常地完成(通过抛出异常导致ExceptionInInitializerError)之后,线程才会被通知。


谢谢。这也是我的理解,我一直认为是这样的。然而,似乎对于那种类型的代码出现了“NullPointerException”。那么,这必然指向JDK的错误吗?你能想到除了JDK错误之外的其他原因吗? - sjlee
@sjlee 请将堆栈跟踪编辑到您的问题中。 - Dioxin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接