有谁能告诉我这个类是否是线程安全的?
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);
}
}
编辑:我不能澄清这个问题。根据{{3}}:
应提供初始化安全性的新保证。如果一个对象被正确构造(这意味着对它的引用在构造期间不会被转义),那么看到对该对象的引用的所有线程也将看到在构造函数中设置的最终字段的值,而不需要同步。
这使我对地图集aMap = new HashMap<String, String>();
感到困惑。所以其他线程可以看到这些
aMap.put("1", "a");
aMap.put("2", "b");
aMap.put("3", "c");
是不是?
编辑:我发现这个JMM FAQ完全关闭了我的问题
答案 0 :(得分:19)
正如已经指出的那样,它绝对是线程安全的,final
因其内存可见性效果而非常重要。
final
的存在保证了其他线程在构造函数完成后在没有任何外部同步的情况下会在地图中看到值。没有final
,在所有情况下都无法保证,并且在将新构造的对象提供给其他线程时,您需要使用安全发布惯用法,即(来自Java Concurrency in Practice):
- 从静态初始化程序初始化对象引用;
- 将对它的引用存储到易失性字段或AtomicReference中;
- 将对它的引用存储到正确构造的对象的最终字段中;或
- 将对它的引用存储到由锁定正确保护的字段中。
答案 1 :(得分:7)
是的。无法修改引用aMap
本身,或在构造函数后添加到地图(禁止反射)。
如果你公开aMap
则不会,因为两个线程可以同时修改地图。
您可以通过Collections.unmodifiableCollection或Collections.unmodifiableMap无法修改aMap
来改善您的课程。
答案 2 :(得分:2)
Guava具有不可变类,可以使这种事情变得更容易并保证不可变:
private final ImmutableMap<String, String> aMap = ImmutableMap.of(
"1", "a",
"2", "b",
"3", "c");
答案 3 :(得分:1)
是的,如果这是整个班级定义而不是其片段。
关键的事实是,aMap
的内容无法在构建后进行修改。
答案 4 :(得分:1)
此类没有并发问题,因为您只公开get方法。如果添加一些修改地图的方法,则必须将此方法标记为synchronized
。
答案 5 :(得分:0)
现在它应该是线程安全的。但是,如果添加其他修改hashmap的方法,则为no。
答案 6 :(得分:0)
我不认为上面的代码片段是线程安全的。唯一符合代码安全的行是
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");
编辑我的错误看到了以后代码中的评论
能够看到字段正确构造的值很好,但如果字段本身是引用,那么您还希望代码查看它指向的对象(或数组)的最新值。如果您的字段是最终字段,则也可以保证。因此,您可以拥有一个指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但是数组内容的值不正确。同样,在这里“正确”,我们的意思是“对象的构造函数结束时的最新”,而不是“最新的可用值”。
答案 7 :(得分:0)
很久以前问过这个问题。但是,我决定回答这个问题。首先,它是完全线程安全的代码。原因如下。
它的状态被很好地封装。类的状态由map及其键值对组成,键对是不变的字符串。因此,通过封装,该类以线程状态方式与其他线程共享其状态。
仅公共方法Foo类提供了get
来发布不可变对象,这些对象对于并行线程是安全的,因为它们无法修改已发布的对象。
map变量为final
,这使其不可变并提供内存可见性。
要回答这个问题,实际上是安全的初始化。没有物体逃逸。其他线程看不到。
答案 8 :(得分:0)
如Java并发实践第16.3节中所述,它必须是线程安全的。
初始化安全性保证正确构造 对象,所有线程将看到最终字段的正确值, 由构造函数设置,无论对象如何 发表。此外,可以通过到达的任何变量 正确构造的对象的最终字段(例如的元素 final数组或final引用的HashMap的内容 字段)也保证对其他线程可见。
对于具有最终字段的对象,初始化安全禁止 用a的初始载荷对结构的任何部分进行重新排序 对该对象的引用。全部写入由 构造函数,以及通过它们可访问的任何变量 字段,在构造函数完成后,以及任何线程都变为“冻结” 获得对该对象的引用的保证将看到一个值 至少与冻结值一样。写道 通过最终字段可访问的初始化变量不会重新排序 在施工后冻结后进行操作
以及该部分的示例:
@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);
}
}