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