最终的静态变量在Java中线程安全吗?

2022-09-01 11:40:03

我已经阅读了很多,但还没有找到一个明确的答案。

我有一个看起来像这样的类:

    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实例访问sharededData是否是线程安全的(如构造函数和doSomethingUseful()所示)。Foo的许多实例将在多线程环境中创建。

我的意图是,sharedData 在静态初始值设定项中初始化,此后不进行修改(只读)。

我所读到的是,不可变对象本质上是线程安全的。但我只在实例变量的上下文中看到了这一点。不可变静态变量线程安全吗?

我发现的另一个结构是PropercurrentHashMap。我可以制作 ConcurrentHashMap 类型的 sharedData,但是它包含的 HashMaps 是否也必须是 ConcurrentHashMap 类型?基本上。。

private static final ConcurrentHashMap<String, HashMap> sharedData;

private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;

还是会更安全(但简单地克隆()成本更高)?

this.myCopyOfData = sharedData.get(key).clone();

腾讯网.

(已编辑静态初始值设定项以提供更多上下文。


答案 1

最终的引用是线程安全的,因为它永远无法更改。Map的内容不是线程安全的,因为它需要最好用Guava实现包装,或者使用包中的Map实现之一。sharedDataImmutableMapjava.util.Collections.unmodifiableMap()java.util.concurrent

只有同时执行这两项操作,您才能在地图上获得全面的线程安全性。任何包含的映射都需要是不可变的,或者也是并发实现之一。

.clone() 从根本上被破坏了,请远离

默认情况下,克隆是一种浅层克隆,它只会返回对容器对象的引用,而不是完整的副本。在关于原因的一般可用信息中有很好的记录。


答案 2

静态初始化块中静态最终字段的初始化是线程安全的。但是,请记住,静态最终参考指向的对象可能不是线程安全的。如果您引用的对象是线程安全的(例如,它是不可变的),那么您就清楚了。

外部 HashMap 中包含的每个 HashMap 都不能保证是线程安全的,除非您按照问题中的建议使用 ConcurrentHashMap。如果不使用线程安全的内部 HashMap 实现,则当两个线程访问同一个内部 HashMap 时,可能会得到意外结果。请记住,只有 ConcurrentHashMap 上的某些操作是同步的。例如,迭代不是线程安全的。


推荐