请考虑以下代码:
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();
...
}
}
encoderFactory
中访问encode()
的行是否会在并发情况下抛出NullPointerException
?
是的,我很清楚,在这种情况下,encoderFactory
很容易被宣布为最终版本,这会使这个问题变得没有实际意义。
但是,我感兴趣的是,上面编写的代码是否仍然安全地发布了encoderFactory
。如果我理解JLS 12.4,那应该是这样的。静态初始化的步骤似乎没有留下任何线程一旦看到类初始化就会看到处于未初始化状态的静态字段(即之前没有发生)的可能性。我认为JLS非常清楚静态初始化会形成内存障碍。
显然已经观察到这样的NullPointerException
,我们最终通过使这个领域最终来修复它。虽然这当然是一件好事,但我仍然感到困惑的是如何看到一个带有这种模式的空指针,否则会出现一个更大的问题,因为它可能意味着非最终静态字段的任何初始分配可能不会可见。
如果假设静态初始化提供内存屏障是一个可靠的(我相信它是),我想这肯定会指向一个JDK错误呢?您能想到除了JDK错误之外可能发生的其他原因吗?
答案 0 :(得分:1)
根据12.4.2:
,它将是线程安全的由于Java编程语言是多线程的,因此初始化类或接口需要仔细同步。
在并发情况下,类的初始化将是安全的。在任何线程有机会调用encode
之前,将加载这些字段,无论该字段是否被声明为final。使用的引用类型没有区别(包括ThreadLocal
)。
他们进一步解释了初始化发生的确切步骤。在初始化成功或突然完成之前(通过引发异常,导致ExceptionInInitializerError
),线程不会得到通知。