Java并发性:final字段(在构造函数中初始化)是线程安全的吗?

Java并发性:final字段(在构造函数中初始化)是线程安全的吗?,java,concurrency,java-memory-model,Java,Concurrency,Java Memory Model,有人能告诉我这个类是否是线程安全的吗 class Foo { private final Map<String,String> aMap; public Foo() { aMap = new HashMap<String, String>(); aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); } pu

有人能告诉我这个类是否是线程安全的吗

class Foo {

    private final Map<String,String> aMap;

    public Foo() {
        aMap = new HashMap<String, String>();
        aMap.put("1", "a");
        aMap.put("2", "b");
        aMap.put("3", "c");
    }

    public String get(String key) {
        return aMap.get(key);
    }

}
还是不


编辑:我发现这与我的问题非常接近

是的,只要这是整个类定义,而不是其中的一个片段


关键的事实是,
aMap
的内容在构建后无法修改。

是的。无法修改参考
aMap
本身,也无法将其添加到构造器之后的映射中(禁止反射)

如果公开
aMap
,则不会公开,因为两个线程可以同时修改映射


您可以通过或使
aMap
不可修改来改进您的类。

现在应该是线程安全的。但是,如果您添加了修改hashmap的其他方法,则不会。

这个类没有并发问题,因为您只公开了一个get方法。如果您添加了一些修改映射的方法,则必须将此方法标记为
已同步

具有不可变类,以使这类操作更简单并保证不可变:

private final ImmutableMap<String, String> aMap = ImmutableMap.of(
    "1", "a",
    "2", "b",
    "3", "c");
private final ImmutableMap aMap=ImmutableMap.of(
“1”、“a”,
“2”、“b”,
“3”、“c”);

正如前面指出的,它是绝对线程安全的,
final
由于其内存可见性效应,在这里非常重要

final
的存在保证了在构造函数完成后,其他线程将在映射中看到值,而无需任何外部同步。如果没有
final
,则无法保证在所有情况下都能使用它,并且在将新构造的对象提供给其他线程时,需要使用安全的发布习惯用法,即(从):

  • 从静态初始值设定项初始化对象引用
  • 将对它的引用存储到易失性字段或原子引用中
  • 将对它的引用存储到正确构造的对象的最终字段中;或
  • 将对它的引用存储到由锁正确保护的字段中

我认为上面的代码片段不是线程安全的。代码安全的唯一一行是

aMap = new HashMap<String, String>();
这意味着一旦初始化了最终字段,就不能保证线程安全。因为只有引用赋值被保证是线程安全的,并且对象本身可以是可变的,正如您的示例所示。以下语句可能不是线程安全的

aMap.put("1", "a");
aMap.put("2", "b");
aMap.put("3", "c");
EDITMy bad稍后看到代码下面的注释

查看字段正确构造的值的能力很好,但是如果字段本身是引用,那么您还希望代码能够查看它所指向的对象(或数组)的最新值。如果您的字段是最终字段,这也是有保证的。因此,您可以拥有指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但数组内容的值不正确。同样,这里的“正确”是指“截至对象构造函数结束时的最新值”,而不是“可用的最新值”


这是很久以前提出的问题。不过,我决定回答这个问题。首先,它是完全线程安全的代码。原因如下

  • 它的状态被很好地封装。类的状态由map及其键值对组成,这些键值对是不可变的字符串。因此,通过封装,该类以线程状态的方式与其他线程共享其状态

  • 只有公共方法Foo类提供了
    get
    ,它发布不可变的对象,这些对象对于并行线程来说是安全的,因为它们不能修改发布的对象

  • map变量是
    final
    ,这使得它不可变并提供内存可见性


  • 回答这个问题,它实际上是安全的初始化。没有物体逃逸。其他线程看不到。

    正如第16.3节Java并发实践中所述,这必须是线程安全的

    初始化安全保证了正确构造 对象,所有线程将看到 是由构造函数设置的,而不管对象是如何设置的 出版。此外,任何可以通过 正确构造的对象的最终字段(例如 最终数组或由最终数组引用的哈希映射的内容 字段)也保证对其他线程可见

    对于具有最终字段的对象,初始化安全禁止 在初始荷载为 对该对象的引用。对最终字段的所有写入由 构造函数,以及通过这些 字段,在构造函数完成时变为“冻结”,以及任何线程 获取对该对象的引用的 这至少与冻结值一样是最新的。写道 不重新排序可通过最终字段访问的初始化变量 施工后冻结后的运营

    这一节的一个例子是:

     @ThreadSafe
     public class SafeStates {
    
        private final Map<String, String> states;
    
        public SafeStates() {
            states = new HashMap<String, String>(); states.put("alaska", "AK");
            states.put("alabama", "AL");
            ...
            states.put("wyoming", "WY");
         }
    
         public String getAbbreviation(String s) { 
            return states.get(s);
         } 
     }
    
    @ThreadSafe
    公共类安全国家{
    私人最终地图国家;
    公共安全国家(){
    states=newhashmap();states.put(“阿拉斯加”、“AK”);
    州。put(“阿拉巴马州”、“阿尔卑斯州”);
    ...
    州。出售(“怀俄明州”、“怀俄明州”);
    }
    公共字符串(字符串s){
    返回状态。获取(s);
    } 
    }
    
    是的,但最后一个关键字的出现与此完全无关,因此看起来仍然有些混乱。如果你能解释一下你所期望发生的事情,我们也许能提供更多帮助。啊哈!看看你现在在问什么。很明显,我花了太多时间在有管理的独生子豆子上。是的,如果您阅读了java语言规范第17.5节的全部内容(您链接的文章对其进行了总结),那么它提到了对象引用
    aMap.put("1", "a");
    aMap.put("2", "b");
    aMap.put("3", "c");
    
     @ThreadSafe
     public class SafeStates {
    
        private final Map<String, String> states;
    
        public SafeStates() {
            states = new HashMap<String, String>(); states.put("alaska", "AK");
            states.put("alabama", "AL");
            ...
            states.put("wyoming", "WY");
         }
    
         public String getAbbreviation(String s) { 
            return states.get(s);
         } 
     }