【问题标题】:Why can an Object member variable not be both final and volatile in Java?为什么 Java 中的 Object 成员变量不能既是 final 又是 volatile?
【发布时间】:2012-02-17 12:32:49
【问题描述】:

如果在一个类中我有一个 ConcurrentHashMap 实例,该实例将被多个线程修改和读取,我可能会这样定义:

public class My Class {

    private volatile ConcurrentHashMap<String,String> myMap = new ConcurrentHashMap<String,String>();
...
}

final 添加到 myMap 字段会导致错误提示我只能使用 final 或 volatile。为什么不能两者兼得?

【问题讨论】:

标签: java multithreading syntax


【解决方案1】:

volatile 仅与变量本身的修改有关,与它所引用的对象无关。拥有final volatile 字段没有意义,因为最终字段无法修改。只需声明字段final 就可以了。

【讨论】:

  • 只是想澄清您的评论“最终字段无法修改”; final 字段实际上是可变的,但是 final 关键字只允许赋值发生一次。
  • @johnterpreneur:这是不正确的; 只有在对象的构造过程中才能将 final 字段分配给,这几乎就是“不可变”的定义。
  • @johntrepreneur:啊,现在我明白了误解发生在哪里了。正如我在答案中所写的那样,字段与其所指的对象之间存在差异-您显然缺少一个重要的区别(或使用了错误的术语)。 final 表示 字段本身 不能被修改,但它仍然可以引用可变对象。该字段的内容只是一个引用(对于非原始类型),而不是一个对象。
  • @corsiKa 这就是为什么对象在构造过程中不应该泄漏的原因。这个问题不是final领域独有的……
  • @shmosel Java 标准没有定义内存屏障。对于 bothfinalvolatile 变量,在读取和写入该值之间存在 happens-before 关系。线程不会通过执行相同值的后续读取来获得额外的保证。每次读取volatile 变量时,像 HotSpot JVM 这样的实现可能会保守地插入一个屏障,但您仍然无法依赖它构建一个正确的程序。当值没有改变时。所以对于不会再改变的final 变量,volatile 没有任何意义。
【解决方案2】:

这是因为 Java 内存模型 (JMM)。

本质上,当您将对象字段声明为final 时,您需要在对象的构造函数中对其进行初始化,然后final 字段不会更改它的值。 JMM 承诺,在 ctor 完成后,任何线程都将看到 final 字段的相同(正确)值。因此,您不需要使用显式同步,例如synchronizeLock 来允许所有线程看到final 字段的正确值。

当您将对象的字段声明为volatile 时,字段的值可能会发生变化,但从任何线程读取的值仍然会看到写入它的最新值。

所以,finalvolatile 达到了相同的目的——对象字段值的可见性,但第一个专门用于一个变量只能分配一次,第二个用于一个可以多次更改的变量.

参考文献:

【讨论】:

  • 这个解释对我来说很好,但我不会说 final 用于“常量”值。这个说法有点误导,虽然我明白你想说什么。
  • '"final and volatile" 达到相同的目的......' 我认为这是本末倒置。 final目的是声明一个变量或字段不能被赋值。 volatile目的是告诉编译器不能通过检查代码来推断该值。 Java 内存模型对这两种变量的可见性有特殊的规则这一事实是次要的,因为它们的非重叠目的。
  • 这是唯一真正解决核心问题的答案,“JMM 承诺,在 ctor 完成后,任何线程都会看到相同(正确)的 final 字段值。”但提供的文档参考不支持它
  • 但是如果我们有final int[] array呢?线程 A 分配了array[index] = 42,线程 B 不保证它会看到这个值。
【解决方案3】:

因为volatilefinal是Java中的两个极端

volatile 表示变量绑定变化

final 表示变量的值永远不会改变

【讨论】:

  • 当您在一个线程上创建容器对象(其最终引用写入一次,是一次,但在某个线程上)然后从另一个线程读取该引用时,就会出现问题
【解决方案4】:

volatile用于其值可能改变的变量,在某些情况下,否则不需要volatile,而final表示变量可能不会改变,所以不需要volatile .

您的并发问题很重要,但是将HashMap设为volatile并不能解决问题,为了处理并发问题,您已经使用ConcurrentHashMap

【讨论】:

  • 您不能制作 HashMap(或任何其他对象)volatile。 'volatile' 关键字影响变量,而不是变量可能引用的对象。
【解决方案5】:

volatile 字段可以保证当您更改它时会发生什么。 (没有可能引用的对象)

一个final字段不能改变(什么字段引用可以改变)

两者兼有是没有意义的。

【讨论】:

  • 问题出现是因为您在一个线程上构造对象,这是最初设置引用的位置,然后可能会在另一个线程上读取该引用。
【解决方案6】:

volatile 修饰符保证所有读写都直接进入主内存,即变量访问几乎进入synchronized 块。这与不能更改的最终变量无关。

【讨论】:

    【解决方案7】:

    因为它没有任何意义。易失性影响对象引用值,而不是对象的字段/等。

    根据您的情况(您有并发地图),您应该填写 final 字段。

    【讨论】: