考虑以下代码:
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 ,应该是这样的。静态初始化的步骤似乎不会让任何线程在看到类已初始化后看到处于未初始化状态的静态字段(即之前没有发生)。我认为 JLS 相当清楚地表明静态初始化形成了内存屏障。
显然已经观察到这样的 NullPointerException
,我们最终通过将此字段设置为 final 来修复它。虽然这当然是一件好事,但我仍然很困惑如何用这种模式看到空指针,否则就会出现更大的问题,因为这可能意味着非最终静态字段的任何初始分配都可能不会可见。
如果静态初始化提供内存屏障的假设是可靠的(我相信它是),那么我想这必然指向一个 JDK 错误?除了 JDK 错误之外,您能想到一个可能发生的原因吗?
最佳答案
它将是线程安全的,根据 12.4.2 :
Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization.
类的初始化在并发情况下是安全的。这些字段将在任何线程有机会调用 encode
之前加载,无论该字段是否被声明为 final。使用的引用类型没有区别(包括 ThreadLocal
)。
他们进一步解释了初始化发生的确切步骤。直到初始化成功或突然完成(通过抛出的异常,导致 ExceptionInInitializerError
),线程才会收到通知。
关于java - 非最终静态字段的静态初始化安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36116736/