Java并发:final字段(在构造函数中初始化)是线程安全的吗?
谁能告诉我这个类是否是线程安全的?
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
正如已经指出的,它是绝对线程安全的,并且由于其内存可见性影响,
final
在这里很重要。final
的存在保证了其他线程在构造函数完成后可以看到映射中的值,而无需任何外部同步。如果没有final
,则无法在所有情况下都得到保证,并且在使新构造的对象可供其他线程使用时,您需要使用安全发布习惯用法,即(来自Java并发实践):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. Withoutfinal
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):是的。无法修改引用
aMap
本身,或在构造函数之后添加到地图(除非反射)。如果您公开
aMap
,则不会,因为两个线程可以同时修改地图。您可以通过 Collections.unmodifyingCollection 或 Collections.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.Guava 具有不可变类,可以使此类事情变得更容易并保证不可变:
Guava has immutable classes for making this sort of thing easier and guaranteed immutable:
是的,只要这是整个类定义而不是其中的片段。
关键事实是
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.这个类没有并发问题,因为您只公开了一个 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
.现在它应该是线程安全的。但是,如果您添加其他修改哈希图的方法,则不会。
As it is now it should be thread-safe. However if you add other methods which modify the hashmap then no.
我认为上面的代码片段不是线程安全的。代码安全的唯一行是
按照 http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html,
这意味着一旦最终字段被初始化,就无法保证线程安全。因为只有引用分配才能保证线程安全,并且对象本身可以根据您的示例是可变的。以下语句可能不是线程安全的
编辑我不好稍后看到代码下面的注释
I don't think the above code snippet is thread safe. The only line that is code safe is
As per example given in http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html,
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
EDIT My bad saw the comments below the code later
这是很久以前问过的问题。不过,我决定回答这个问题。首先,它是完全线程安全的代码。原因如下。
它的状态封装得很好。类的状态由映射及其键值对组成,键值对是不可变的字符串。因此,通过封装,类以线程状态的方式与其他线程共享其状态。
只有公共方法 Foo 类提供
get
,它发布不可变对象,这些对象对于并行线程来说是安全的,因为它们无法修改已发布的对象。映射变量是
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.
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.
Only public method Foo class provides
get
which publishes immutable objects which are safe for parallel threads because they can't modify published object.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.
正如 Java 并发实践中第 16.3 节所述,这必须是线程安全的。
以及该部分的示例:
As it is described in Java Concurrency in Practice in section 16.3, this has to be thread-safe.
And an example from that section: