举一个更具体的例子来说明我在评论中所说的内容可能会有所帮助。假设我做了这样的事情:
public class CHMHolder {
private /*non-final*/ CHMHolder instance;
public static CHMHolder getInstance() {
if (instance == null) {
instance = new CHMHolder();
}
return instance;
}
private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
public ConcurrentHashMap<String, String> getMap() {
return map;
}
}
现在,这不是线程安全的,原因有很多!但是假设 threadA 看到 的null
值instance
并因此实例化CHMHolder
,然后 threadB 巧合地看到了同一个CHMHolder
实例(这不能保证,因为没有同步)。您会认为 threadB 看到的是非null
CHMHolder.map
- ,对吧?可能不会,因为在 threadA'smap = new ...
和 threadB's之间没有正式的发生前边缘return map
。
这在实践中意味着类似的东西CHMHolder.getInstance().getMap().isEmpty()
可能会抛出一个NullPointerException
,这会令人困惑——毕竟,getInstance
看起来它应该总是返回一个非null
CHMHolder
,而且CHMHolder
看起来它应该总是有一个非null
映射。啊,多线程的乐趣!
如果map
被标记final
,则 user2864740 引用的 JLS 位适用。这意味着如果 threadB 看到与 threadA 看到的实例相同的实例(同样,它可能不会看到),那么它也会看到执行的map = new...
操作threadA
——也就是说,它会看到非null
CHM 实例。一旦看到这一点,CHM 的内线程安全将足以确保安全访问。