此代码中的volatile是多余的吗?
public class Test {
private volatile Map<String, String> map = null;
public void resetMap() { map = new ConcurrentHashMap<>(); }
public Map<String, String> getMap() { return map; }
}
换句话说,map = new ConcurrentHashMap<>();
是否提供任何可见性保证?
据我所知,ConcurrentMap
提供的唯一保证是:
在将对象作为键或值放入ConcurrentMap之前的线程中的操作发生在从另一个线程中的ConcurrentMap访问或删除该对象之后的操作之前。
java.util.concurrent中的其他线程安全集合(CopyOnWriteArrayList等)怎么样?
答案 0 :(得分:8)
volatile
并不是多余的,因为您正在更改对地图的引用。即ConcurrentMap仅提供关于集合内容的保证人,而不是对其的引用。
另一种选择是
public class Test {
private final Map<String, String> map = new ConcurrentHashMap<>();
public void resetMap() { map.clear(); }
public Map<String, String> getMap() { return map; }
}
java.util.concurrent中的其他线程安全集合(CopyOnWriteArrayList等)怎么样?
只有集合的行为是线程安全的。对集合的引用不是线程安全的,通过将集合中的元素添加到集合中,集合中的元素不会成为线程安全的。
答案 1 :(得分:6)
volatile
。它适用于参考,而不适用于它所引用的内容。换句话说,对象是线程安全的并不重要,其他线程将看不到map
字段的新值(例如,可能会看到先前引用的并发映射或null
)。
此外,即使您的对象是不可变的(例如String
),您仍然需要volatile
,更不用说其他线程安全的集合,例如CopyOnWriteArrayList
。
答案 2 :(得分:4)
这不仅仅是参考文献。通常,如果没有volatile
修饰符,其他线程可能会观察到对象的新引用,但会以部分构造的状态观察对象。一般而言,即使在查阅文档后,也不容易知道哪些对象可以安全地通过数据竞争发布。一个有趣的说明是JLS确实为线程安全的不可变对象保证了这一点,所以如果文档提到这两个属性就足够了。
ConcurrentHashMap
显然不是一个不可变对象,因此不适用,并且文档没有提及有关数据竞争发布的任何内容。通过仔细检查源代码,我们可以得出结论它确实是安全的,但是我不建议在没有明确记录此属性的情况下依赖这些结果。
答案 3 :(得分:0)
<强> Memory Consistency Properties 强>
在对该相同字段的每次后续读取之前发生对易失性字段的写入。易失性字段的写入和读取具有与进入和退出监视器类似的内存一致性效果,但不需要互斥锁定。
在将对象放入任何并发集合之前的线程中的操作发生在从另一个线程中的集合访问或删除该元素之后的操作之前。
答案 4 :(得分:0)
好的 - 如果字段不易变,我能够构建一个断开(在我的机器上:JDK 1.7.06 / Win 7 64位)的示例 - 如果map不是volatile,程序永远不会打印Loop exited
- 如果map是volatile,它会打印Loop exited
。 QED。
public class VolatileVisibility extends Thread {
Map<String, String> stop = null;
public static void main(String[] args) throws InterruptedException {
VolatileVisibility t = new VolatileVisibility();
t.start();
Thread.sleep(100);
t.stop = new ConcurrentHashMap<>(); //write of reference
System.out.println("In main: " + t.stop); // read of reference
System.out.println("Waiting for run to finish");
Thread.sleep(200);
System.out.println("Still waiting");
t.stop.put("a", "b"); //write to the map
Thread.sleep(200);
System.exit(0);
}
public void run() {
System.out.println("In run: " + stop); // read of reference
while (stop == null) {
}
System.out.println("Loop exited");
}
}
答案 5 :(得分:0)
我的印象是Doug Lea的并发对象可以通过数据竞争安全地发布,因此即使被滥用也是“线程安全的”。虽然他可能不会公开宣传。