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

最终的静态变量在Java中是线程安全的吗?,java,multithreading,static,hashmap,final,Java,Multithreading,Static,Hashmap,Final,我读了很多书,但还没有找到一个明确的答案 我有一个类似这样的类: public class Foo() { private static final HashMap<String, HashMap> sharedData; private final HashMap myRefOfInnerHashMap; static { // time-consuming initialization of sh

我读了很多书,但还没有找到一个明确的答案

我有一个类似这样的类:

    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()) {
                ...
            }
        }
     }
蒂亚


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

是的,这也是线程安全的。静态类的所有最终成员都将在允许任何线程访问它们之前进行初始化

如果初始化期间
静态
块失败,则在第一次尝试初始化的线程中将引发
异常InInitializeRerror
。随后尝试引用该类将引发一个
NoClassDefFoundError

一般来说,
HashMap
的内容不能保证跨线程的可见性。但是,类初始化代码使用
synchronized
块来防止多个线程初始化类。此同步将刷新映射(及其包含的
HashMap
实例)的状态,以便所有线程都能正确看到它们,前提是在类初始值设定项之外不对映射或其包含的映射进行任何更改


有关类初始化和同步要求的信息,请参见。

对于
最终静态变量而言,没有任何本质上的线程安全性。声明成员变量
final static
只能确保此变量只分配一次

线程安全问题与如何声明变量关系不大,而是取决于如何与变量交互。因此,如果没有关于您的计划的更多详细信息,就不可能回答您的问题:

  • 多个线程是否修改
    sharedData
    变量的状态
  • 如果是,您是否对
    sharedData
    的所有写入(和读取)进行同步
使用ConcurrentHashMap只能保证
Map
的各个方法是线程安全的,它不会使这样的操作成为线程安全的:

if (!map.containsKey("foo")) {
    map.put("foo", bar);
}

在这种情况下,只有sharedData对象是不可变的,这意味着您将一直使用同一个对象。但其中的任何数据都可以随时从任何线程更改(删除、添加等)。

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


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

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

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

.clone()已基本崩溃,请远离
默认情况下,克隆是浅层克隆,它只返回对容器对象的引用,而不是完整副本。通常可用的信息中都有关于为什么的详细说明。

什么是线程安全?当然,HashMap的初始化是线程安全的,因为所有Foo都共享同一个Map实例,并且除非在静态init中发生异常,否则保证映射在那里


但是修改映射的内容肯定不是线程安全的。静态final表示无法将映射共享数据切换到其他映射。但地图的内容是另一个问题。如果给定的密钥在同一时间被多次使用,您可能会遇到并发问题。

否。除非它们是不可变的

他们唯一能做的就是

  • 级别可接受
  • 避免要更改的引用
但是,如果属性是可变的,那么它就不是线程安全的

另见:


这是完全一样的,只是他们是班级级别的

您不是在问
sharedData
的静态初始化是否是线程安全的,并且只执行一次吗

是的,情况就是这样


当然,这里的许多人都正确地指出,
sharedData
的内容仍然可以修改。

最终静态变量本身就具有线程安全性,因为final意味着它只能被赋值一次,并且赋值必须在隐式同步的类初始化期间进行。当然,这并没有说明此类变量所引用的对象是安全的,但例如,最终静态long或double是安全的,而实例或非最终long或double是不安全的。将任何内容设置为final本质上就是将该变量设置为线程安全的引用。在没有同步的情况下,在多个线程之间共享最终映射是不安全的。这就是我所说的“最终静态变量没有本质上的线程安全性”。@fuzzy lollipop,引用可能是线程安全的,但是映射中的状态呢?这就是关键所在。只要不进行后续更改,该状态将被同步:它不是不变的。这似乎是final的一个常见误解,它只会阻止sharedData的重新分配。@Robin-他指出,“其中的任何数据都可以更改。”
private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;
this.myCopyOfData = sharedData.get(key).clone();
if (!map.containsKey("foo")) {
    map.put("foo", bar);
}