当在使用java.util.concurrent.Concurrent*容器时使用volatile关键字有什么作用?

3
当我看到这段代码时,问题就出现了:
private static volatile ConcurrentHashMap<String, String> cMap = null;
static {
    cMap = new ConcurrentHashMap<String, String>();
}

对我来说,这里的volatile是多余的,因为容器是ConcurrentHashMap,根据JavaDoc,它已经有同步puts,天啊,使用的类只实例化一次,没有任何设置或获取它的方法。

我唯一看到的volatile提供的是,如果我将cMap在不久的将来设置为引用一个新对象,那么这些读写将被同步。

我有什么遗漏吗?

4个回答

7
volatile修饰符与所涉及的类无关,它只与变量cMap有关。它只影响线程获取或更改该变量的值的方式。当您调用引用对象上的方法时,您已经超出了volatile的范围。
正如您所说,它基本上确保所有线程都能够看到对cMap值的更改(即使其引用不同的映射)。
这可能是一个好主意,也可能不是,这取决于代码的其他部分。例如,如果您可以将其设置为final,则无需将其设置为volatile...

是的,将其设为最终版是有意义的。 - Bleadof

5

除非变量在后面被重新赋值,否则volatile是完全不必要的。

任何在静态初始化程序中发生的写入都对使用该类的任何代码可见(即当访问静态方法/字段时,当调用构造函数时)。

如果没有这个保证,我们就会陷入严重的麻烦。数百万行的代码将是错误的。

请参见JLS3 12.4.2:

初始化类或接口的过程如下:

  1. 在表示要初始化的类或接口的Class对象上同步(§14.19)。

2
声明cMap引用为volatile可以确保其初始化值对所有线程可见。据我所知,如果没有这个关键字,就不能保证,即一些线程可能会看到一个null引用,而不是对正确初始化的映射对象的引用。 [更新]正如@irreputable指出的(并在Java Concurrency in Practice第3.5.3节中解释的那样),我在上面的陈述是错误的:静态初始化程序确实由JVM在类初始化时执行,由JVM的内部同步进行保护。因此,volatile是不必要的。[/更新] 另一方面,将其声明为final(并立即初始化,而不是在单独的static块中)也会保证可见性。
请注意,引用是否为volatile与其所引用的变量的类无关。即使所引用的类是线程安全的,引用本身也可能不是。

@irreputable,糟糕,你是对的。我的错 :-( 已经修复并+1给你 :-) - Péter Török

1

只有在cMap的值发生变化时才需要声明它为volatile。声明它为volatile并不意味着该映射中包含的对象也是如此。

如果cMap发生更改,则所有线程都需要看到CHM的最新值。 话虽如此,我强烈建议将cMap设置为final。非final静态变量可能会很危险。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接