我在某处读到,即使ConcurrentHashMap
在多个线程中使用是安全的,也应该声明为final
,甚至是private final
。我的问题如下:
1)CocurrentHashMap
是否仍保持线程安全而不将其声明为final
?
2)关于私有关键字的相同问题。可能最好问一般问题 - public/private
关键字会影响运行时行为吗?我理解它们在内部/外部类的可见性/用法方面的含义,但在多线程运行时的上下文中意味着什么呢?我认为像public ConcurrentHashMap
这样的代码可能只在编码样式术语中不正确,而不是在运行时,我是对的吗?
答案 0 :(得分:5)
在评论中提供我所谈论的更具体的例子可能会有所帮助。假设我做的是这样的事情:
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
的{{1}}值,从而实例化instance
,然后通过一个愉快的巧合,threadB看到相同的CHMHolder
实例(这是不保证,因为没有同步)。您会认为threadB看到非CHMHolder
null
,对吧?它可能没有,因为在threadA的CHMHolder.map
和threadB的map = new ...
之间没有正式的发生边缘。
这在实践中意味着像return map
这样的东西可能会引发CHMHolder.getInstance().getMap().isEmpty()
,这会让人感到困惑 - 毕竟,NullPointerException
看起来应该总是返回非getInstance
1}} null
和CHMHolder
看起来总是应该有非CHMHolder
地图。啊,多线程的乐趣!
如果null
被标记为map
,那么user2864740引用的JLS位适用。这意味着如果threadB看到了threadA看到的同一个实例(同样,它可能没有),那么它也会看到final
所做的map = new...
动作 - 也就是说,它会看到非threadA
CHM实例。一旦看到,CHM的内部线程安全就足以确保安全访问。
答案 1 :(得分:3)
final
和private
对所述变量命名的对象的thread-safety(或缺少)没有任何说明。 (他们修改变量,而不是对象。)无论如何..
变量如果是final field,则会在各个主题中保持一致:
当构造函数完成时,对象被认为是完全初始化的。在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的
final
字段的正确初始化值。
实际的ConcurrentHashMap 对象是“线程安全的”,只要其保证。特别是,只保证单个方法调用/操作,因此可能需要使用更大的同步代码。如果只能从创建它的对象访问CHM,则可以轻松控制。
使用private
通常被认为是好的,因为它阻止其他代码“意外地”访问变量(以及它所命名的对象)。但是,私有修饰符不建立与final
修饰符相同的发生前保证,因此与线程安全性正交。