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

发布于 2024-11-16 18:21:17 字数 957 浏览 8 评论 0原文

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

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);
    }

}

编辑:我的错是没有澄清这个问题。根据 JMM 常见问题解答

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

这让我混淆了 aMap 的设置是 aMap = new HashMap();。那么其他线程是否可以看到这些

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

编辑:我发现这个问题正是关闭我的问题

Can anyone tell me whether this class is threadsafe or not ?

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);
    }

}

Edit: It my fault to not clarify the question. According to JMM FAQ :

A new guarantee of initialization safety should be provided. If an object is properly constructed (which means that references to it do not escape during construction), then all threads which see a reference to that object will also see the values for its final fields that were set in the constructor, without the need for synchronization.

This made me confuse that the set to aMap is aMap = new HashMap<String, String>();. So other threads can see these

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

or not ?

Edit: I found this question that exactly closed to my question

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

七堇年 2024-11-23 18:21:17

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

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

  • 从静态初始化器初始化对象引用;
  • 将对它的引用存储到易失性字段或 AtomicReference 中;
  • 将对它的引用存储到正确构造的对象的最终字段中;或
  • 将对它的引用存储到由锁正确保护的字段中。

As already pointed out it's absolutely thread-safe, and final is important here due to its memory visibility effects.

Presence of final guarantees that other threads would see values in the map after constructor finished without any external synchronization. Without final it cannot be guaranteed in all cases, and you would need to use safe publication idioms when making newly constructed object available to other threads, namely (from Java Concurrency in Practice):

  • Initializing an object reference from a static initializer;
  • Storing a reference to it into a volatile field or AtomicReference;
  • Storing a reference to it into a final field of a properly constructed object; or
  • Storing a reference to it into a field that is properly guarded by a lock.
辞别 2024-11-23 18:21:17

是的。无法修改引用 aMap 本身,或在构造函数之后添加到地图(除非反射)。

如果您公开aMap,则不会,因为两个线程可以同时修改地图。

您可以通过 Collections.unmodifyingCollectionCollections.unmodifyingMap。

Yes it is. There is no way to modify the reference aMap itself, or add to the map after the constructor (barring reflection).

If you expose aMap it will not be, because two threads could then modify the map at the same time.

You could improve your class by making aMap unmodifiable via Collections.unmodifiableCollection or Collections.unmodifiableMap.

被你宠の有点坏 2024-11-23 18:21:17

Guava 具有不可变类,可以使此类事情变得更容易并保证不可变:

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

Guava has immutable classes for making this sort of thing easier and guaranteed immutable:

private final ImmutableMap<String, String> aMap = ImmutableMap.of(
    "1", "a",
    "2", "b",
    "3", "c");
腹黑女流氓 2024-11-23 18:21:17

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

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

Yes it is, provided this is the entire class definition and not a snippet thereof.

The key fact is that there is no way the contents of aMap can be modified post-construction.

把梦留给海 2024-11-23 18:21:17

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

This class has no concurrency issue cause you expose only a get method. If you add some method that modify the map you have to mark this method as synchronized.

〃温暖了心ぐ 2024-11-23 18:21:17

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

As it is now it should be thread-safe. However if you add other methods which modify the hashmap then no.

高速公鹿 2024-11-23 18:21:17

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

aMap = new HashMap<String, String>();

按照 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 = f.x; // x is guaranteed to be 3
       int j = f.y; // y can have any value 
     }
   }
}

这意味着一旦最终字段被初始化,就无法保证线程安全。因为只有引用分配才能保证线程安全,并且对象本身可以根据您的示例是可变的。以下语句可能不是线程安全的

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

编辑我不好稍后看到代码下面的注释

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

I don't think the above code snippet is thread safe. The only line that is code safe is

aMap = new HashMap<String, String>();

As per example given in 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 = f.x; // x is guaranteed to be 3
       int j = f.y; // y can have any value 
     }
   }
}

This means that once the final fields are initialized there is no thread safety guaranteed. Since only the reference assignment is guaranteed to be thread safe and object itself can be mutable as per your example. Following statement might not be thread safe

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

EDIT My bad saw the comments below the code later

The ability to see the correctly constructed value for the field is nice, but if the field itself is a reference, then you also want your code to see the up to date values for the object (or array) to which it points. If your field is a final field, this is also guaranteed. So, you can have a final pointer to an array and not have to worry about other threads seeing the correct values for the array reference, but incorrect values for the contents of the array. Again, by "correct" here, we mean "up to date as of the end of the object's constructor", not "the latest value available".

零度℉ 2024-11-23 18:21:17

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

  1. 它的状态封装得很好。类的状态由映射及其键值对组成,键值对是不可变的字符串。因此,通过封装,类以线程状态的方式与其他线程共享其状态。

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

  3. 映射变量是final,这使其不可变并提供内存可见性。

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

It's question asked long time ago. However, I have decided to answer the question. First of all, it is totally thread safe code. The reasons are the following.

  1. Its state is well-encapsulated. The class's state consists of map and its key value pairs which are strings which are immutable. So, through encapsulation the class shares its state with other threads in thread-state manner.

  2. Only public method Foo class provides get which publishes immutable objects which are safe for parallel threads because they can't modify published object.

  3. map variable is final which makes it immutable and provides memory visibility.

To answer the question, it's actually safe initialization. No objects are escaping. Other threads does not see.

爱你不解释 2024-11-23 18:21:17

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

初始化安全保证了正确构造
对象,所有线程都会看到最终字段的正确值
由构造函数设置,无论对象如何
发表。此外,任何可以通过
正确构造的对象的最终字段(例如
最终数组或由最终引用的 HashMap 的内容
字段)也保证对其他线程可见。

对于具有final字段的对象,初始化安全禁止
使用初始载荷重新排序施工的任何部分
对该对象的引用。所有写入最终字段的操作均由
构造函数,以及通过这些构造函数可访问的任何变量
字段,当构造函数完成时变得“冻结”,并且任何线程
获得对该对象的引用保证看到一个值
该值至少与冻结值一样最新。写道
初始化可通过 Final 字段访问的变量不会重新排序
施工后冻结后的运营

以及该部分的示例:

 @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);
     } 
 }

As it is described in Java Concurrency in Practice in section 16.3, this has to be thread-safe.

Initialization safety guarantees that for properly constructed
objects, all threads will see the correct values of final fields that
were set by the constructor, regardless of how the object is
published. Further, any variables that can be reached through a
final field of a properly constructed object (such as the elements of
a final array or the contents of a HashMap referenced by a final
field) are also guaranteed to be visible to other threads.

For objects with final fields, initialization safety prohibits
reordering any part of construction with the initial load of a
reference to that object. All writes to final fields made by the
constructor, as well as to any variables reachable through those
fields, become “frozen” when the constructor completes, and any thread
that obtains a reference to that object is guaranteed to see a value
that is at least as up to date as the frozen value. Writes that
initialize variables reachable through final fields are not reordered
with operations following the post-construction freeze

And an example from that section:

 @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);
     } 
 }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文