6

这段代码中 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 等)中的其他线程安全集合怎么样?

4

6 回答 6

9

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 等)中的其他线程安全集合怎么样?

只有集合的行为是线程安全的。对集合的引用不是线程安全的,集合中的元素不会通过将它们添加到集合来实现线程安全。

于 2012-08-29T11:46:07.170 回答
6

volatile这里是必要的。它适用于引用,而不适用于它所引用的内容。换句话说,一个对象是线程安全的并不重要,其他线程不会看到map字段的新值(例如,可能会看到以前引用的并发映射或null)。

此外,即使您的对象是不可变的(例如String),您仍然需要volatile,更不用说其他线程安全的集合,例如CopyOnWriteArrayList.

于 2012-08-29T11:47:53.313 回答
4

这不仅仅是关于参考。通常,在没有volatile修饰符的情况下,其他线程可能会观察到对对象的新引用,但会观察到处于部分构造状态的对象。一般来说,即使在查阅了文档之后,也很难知道哪些对象可以安全地被数据竞赛发布。一个有趣的说明是JLS确实为线程安全的不可变对象保证了这一点,所以如果文档提到这两个属性就足够了。

ConcurrentHashMap显然不是一个不可变的对象,所以这不适用,并且文档没有提到任何关于数据竞赛发布的内容。通过仔细检查源代码,我们可能会得出结论,它确实是安全的,但是我不建议在没有明确记录此属性的情况下依赖此类发现。

于 2012-08-29T11:52:45.910 回答
0

内存一致性属性

对 volatile 字段的写入发生在对同一字段的每次后续读取之前。volatile 字段的写入和读取具有与进入和退出监视器类似的内存一致性效果,但不需要互斥锁定。

在将对象放入任何并发集合之前的线程中的操作发生在另一个线程中从集合中访问或删除该元素之后的操作。

于 2012-08-29T11:55:01.057 回答
0

好的 - 如果字段不是易失性,我能够构建一个中断的示例(在我的机器上:JDK 1.7.06 / Win 7 64 位) - 如果地图不是易失性,程序永远不会打印- 如果地图Loop exited是易失性的,它会打印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");
    }
}
于 2012-08-29T12:16:48.357 回答
0

我的印象是 Doug Lea 的并发对象可以通过数据竞争安全地发布,因此即使被滥用,它们也是“线程安全的”。尽管他可能不会公开宣传。

于 2012-08-30T00:43:25.530 回答