Java中的最终静态变量是否安全?

psc*_*ang 26 java static multithreading final hashmap

我已经阅读了很多,但没有找到明确的答案.

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

    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()) {
                ...
            }
        }
     }
Run Code Online (Sandbox Code Playgroud)

我想知道从Foo实例访问sharedData是否是线程安全的(如构造函数和doSomethingUseful()中所示).Foo的许多实例将在多线程环境中创建.

我的意图是sharedData在静态初始化程序中初始化,之后不再修改(只读).

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

我找到的另一个构造是ConcurrentHashMap.我可以创建ConcurrentHashMap类型的sharedData但是它包含的HashMaps也必须是ConcurrentHashMap类型的?基本上..

private static final ConcurrentHashMap<String, HashMap> sharedData;
Run Code Online (Sandbox Code Playgroud)

要么

private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;
Run Code Online (Sandbox Code Playgroud)

或者它会更安全(简单克隆()更昂贵)?

this.myCopyOfData = sharedData.get(key).clone();
Run Code Online (Sandbox Code Playgroud)

TIA.

(已编辑静态初始化程序以提供更多上下文.)

小智 24

参考sharedData这是最后的是线程安全的,因为它永远不会改变.Map的内容不是线程安全的,因为它需要最好用Guava ImmutableMap实现包装,java.util.Collections.unmodifiableMap()或者使用java.util.concurrent包中的Map实现之一.

只有当你做BOTH将你对地图综合线程安全.任何包含的Maps都需要是不可变的或者是并发实现之一.

.clone()从根本上被打破,远离

默认情况下克隆是一个浅层克隆,它只返回对容器对象的引用而不是完整的副本.有关原因的一般可用信息中有详细记录.

  • 是的,但如果地图应该是不可变的,它将作为未来维护者意图的文档.更喜欢__explicit__而不是隐式 (3认同)
  • 我知道了.所以,简而言之,"final"+"unmodifiableMap()"= immutability =>线程安全. (3认同)
  • "将不会被更改"与"无法更改"不同,您应该使用java.util.Collections.unmodifiableMap()使它们不可变,因此它们"无法"进行更改.只是使变量final不会使容器线程安全.只有包装器方法才能做到这一点. (2认同)

Mik*_*els 8

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

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

  • 为了更清晰,我建议将"访问"更改为"更新".如果您可以通过其他方式保证所有后续访问都是只读的,那么任何类都是线程安全的(因此,即使没有锁定或不变性,某些访问也是线程安全的).设计不变性使得在执行此保证时更容易证明线程安全性. (3认同)
  • @freshfunk,迭代一个内容永远不会改变的容器是安全的.如果内容可以改变则不安全. (2认同)

ext*_*eon 6

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

但是,修改Map的内容绝对不是线程安全的.静态final意味着无法为另一个Map切换Map sharedData.但地图的内容是一个不同的问题.如果给定的密钥同时使用多次,则可能会出现并发问题.


eri*_*son 5

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

如果该static块在初始化期间失败,ExceptionInInitializerError则将在第一次尝试初始化的线程中引发an 。随后尝试引用该类将引发NoClassDefFoundError

通常,a的内容HashMap不能保证跨线程可见。但是,类初始化代码使用一个synchronized块来防止多个线程初始化该类。此同步将刷新映射(及其HashMap包含的实例)的状态,以便对所有线程正确可见-假设在类初始化器之外未对映射或其所包含的映射进行任何更改。

有关类初始化和同步要求的信息请参见Java语言规范,第12.4.2节


Osc*_*Ryz 5

不,除非它们是不可变的.

他们唯一做的就是

  • 是班级可访问
  • 避免引用被更改.

如果你的属性是可变的,那么它不是线程安全的.

另请参阅:我们是否同步最终的实例变量?

除了属于班级之外,它完全相同.