我已经阅读了很多,但没有找到明确的答案。
我有一个看起来像这样的课程:
public class Foo() {
private static final HashMap<String, HashMap> sharedData;
private final HashMap myRefOfInnerHashMap;
static {
// time-consuming initialization of sharedData
final HashMap<String, String> innerMap = new HashMap<String, String>;
innerMap.put...
innerMap.put...
...a
sharedData.put(someKey, java.util.Collections.unmodifiableMap(innerMap));
}
public Foo(String key) {
this.myRefOfInnerHashMap = sharedData.get(key);
}
public void doSomethingUseful() {
// iterate over copy
for (Map.Entry<String, String> entry : this.myRefOfInnerHashMap.entrySet()) {
...
}
}
}
我想知道从Foo实例访问sharedData是否是线程安全的(如构造函数和doSomethingUseful()中所示)。 Foo的许多实例将在多线程环境中创建。
我的意图是在静态初始化程序中初始化sharedData,之后不再修改(只读)。
我读过的是不可变对象本质上是线程安全的。但我只是在实例变量的上下文中看到了这一点。不可变的静态变量是否安全?
我找到的另一个构造是ConcurrentHashMap。我可以创建ConcurrentHashMap类型的sharedData但是它包含的HashMaps也必须是ConcurrentHashMap类型的?基本上..
private static final ConcurrentHashMap<String, HashMap> sharedData;
或
private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;
或者它会更安全(简单克隆()更昂贵)?
this.myCopyOfData = sharedData.get(key).clone();
TIA。
(已编辑静态初始化程序以提供更多上下文。)
答案 0 :(得分:21)
引用到sharedData
,最终是线程安全的,因为它永远不会被更改。 Map的内容是 NOT 线程安全,因为它需要包含优选的Guava ImmutableMap
实现或java.util.Collections.unmodifiableMap()
,或者使用java.util.concurrent
中的一个Map实现。 1}}包。
只有当您执行 BOTH 时,才能在地图上获得全面的线程安全性。任何包含的Maps都需要是不可变的或者是并发实现之一。
默认情况下克隆是一个浅层克隆,它只返回对容器对象的引用而不是完整的副本。有关原因的一般可用信息中有详细记录。
答案 1 :(得分:8)
静态初始化块中静态final字段的初始化是线程安全的。但是,请记住静态最终引用点可能不的对象是线程安全的。如果您引用的对象是线程安全的(例如,它是不可变的),那么您就是明确的。
除非您按照问题中的建议使用ConcurrentHashMap,否则不能保证外部HashMap中包含的每个HashMap都是线程安全的。如果不使用线程安全的内部HashMap实现,当两个线程访问相同的内部HashMap时,可能会出现意外结果。请记住,只有ConcurrentHashMap上的某些操作是同步的。例如,迭代不是线程安全的。
答案 2 :(得分:6)
什么是线程安全的?当然,HashMap的初始化是线程安全的,因为所有Foo都共享相同的Map实例,并且除非静态init中发生异常,否则Map保证在那里。
但是,修改Map的内容绝对不是线程安全的。静态final意味着无法为另一个Map切换Map sharedData。但地图的内容是一个不同的问题。如果给定的密钥同时使用多次,则可能会出现并发问题。
答案 3 :(得分:5)
是的,这也是线程安全的。在允许任何线程访问它们之前,将初始化静态类的所有最终成员。
如果static
块在初始化期间失败,则会在首次尝试初始化的线程中引发ExceptionInInitializerError
。随后尝试引用该类将引发NoClassDefFoundError
。
通常,HashMap
的内容无法保证跨线程的可见性。但是,类初始化代码使用synchronized
块来防止多个线程初始化类。此同步将刷新映射的状态(以及它包含的HashMap
实例),以便它们对所有线程都正确可见 - 假设没有对地图或其包含的地图进行任何更改类初始化器。
有关类初始化和同步要求的信息,请参阅Java Language Specification, §12.4.2。
答案 4 :(得分:5)
没有。除非它们是不可变的。
他们唯一做的就是
如果你的属性是可变的,那么它不是线程安全的。
另请参阅:Do we synchronize instances variables which are final?
除了属于班级之外,它们完全相同。
答案 5 :(得分:3)
final static
变量没有固有的线程安全性。声明成员变量final static
仅确保将此变量分配给一次。
线程安全问题与声明变量的方式关系不大,而是依赖于您与变量的交互方式。因此,如果没有关于您的计划的更多详细信息,则无法回答您的问题:
sharedData
变量的状态?sharedData
的所有写入(和读取)?使用ConcurrentHashMap只能保证Map
的各个方法都是线程安全的,它不会进行诸如此线程安全的操作:
if (!map.containsKey("foo")) {
map.put("foo", bar);
}
答案 6 :(得分:1)
您是否真的在询问sharedData
的静态初始化是否是线程安全的并且只执行一次?
是的,就是这样。
当然,很多人都正确地指出sharedData
的内容仍然可以修改。
答案 7 :(得分:0)
在这种情况下,只有sharedData对象是immutable,这意味着只有你将使用相同的对象。但是它内部的任何数据都可以随时从任何线程更改(删除,添加等)。