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

时间:2010-03-09 19:59:04

标签: java multithreading static hashmap final

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

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

    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。

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

8 个答案:

答案 0 :(得分:21)

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

只有当您执行 BOTH 时,才能在地图上获得全面的线程安全性。任何包含的Maps都需要是不可变的或者是并发实现之一。

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

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

答案 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,这意味着只有你将使用相同的对象。但是它内部的任何数据都可以随时从任何线程更改(删除,添加等)。

相关问题