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

谁能告诉我这个类是否是线程安全的?

class Foo { private final Map aMap; public Foo() { aMap = new HashMap(); aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); } public String get(String key) { return aMap.get(key); } } 

编辑:我不能澄清这个问题。 根据JMM FAQ :

应提供初始化安全性的新保证。 如果正确构造了一个对象(这意味着对它的引用在构造期间不会被转义),那么看到对该对象的引用的所有线程也将看到在构造函数中设置的最终字段的值,而不需要同步。

这让我感到困惑的是,set到aMap是aMap = new HashMap(); 。 所以其他线程可以看到这些

 aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); 

或不 ?

编辑:我发现这个问题与我的问题完全不同

正如已经指出的那样,它绝对是线程安全的,并且由于其内存可见性效果, final在这里很重要。

存在final保证其他线程在构造函数完成后在没有任何外部同步的情况下在地图中看到值。 没有final ,在所有情况下都无法保证,并且在将新构造的对象提供给其他线程时,您需要使用安全的发布惯用法 ,即(来自Java Concurrency in Practice ):

  • 从静态初始化程序初始化对象引用;
  • 将对它的引用存储到volatile字段或AtomicReference中;
  • 将对它的引用存储到正确构造的对象的最终字段中; 要么
  • 将对它的引用存储到由锁正确保护的字段中。

是的。 没有办法修改引用aMap本身,或者在构造函数之后添加到地图(禁止reflection)。

如果你公开一个aMap它就不会,因为两个线程可以同时修改地图。

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

Guava有一些不可变的类,可以使这种事情变得更容易并保证不可变:

 private final ImmutableMap aMap = ImmutableMap.of( "1", "a", "2", "b", "3", "c"); 

此类没有并发问题,因为您只公开get方法。 如果添加一些修改地图的方法,则必须将此方法标记为已synchronized

是的, 只要这是整个class级定义而不是其片段

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

现在它应该是线程安全的。 但是,如果添加其他修改hashmap的方法,则为no。

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

 aMap = new HashMap(); 

根据http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html中给出的示例,

 class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample() { x = 3; y = 4; } static void writer() { f = new FinalFieldExample(); } static void reader() { if (f != null) { int i = fx; // x is guaranteed to be 3 int j = fy; // y can have any value } } } 

这意味着一旦初始化了最终字段,就不会保证线程安全。 由于只保证引用赋值是线程安全的,因此根据您的示例,对象本身可以是可变的。 以下语句可能不是线程安全的

 aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); 

编辑我的坏看到后面的代码下面的评论

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