Java中final和volatile字段之间的内存模型语义的差异

时间:2016-03-25 07:56:35

标签: java multithreading concurrency thread-safety volatile

来自Java Concurrency In Practice:

  

当一个字段被声明为volatile时,编译器和运行时被打开   注意这个变量是共享的,对它的操作应该是   不能与其他内存操作重新排序。波动变量是   没有缓存在寄存器或缓存中,而是隐藏在其他寄存器中   处理器,因此读取volatile变量总是返回最多   最近由任何一个帖子写的。(p25)

  

无法修改最终字段(尽管它们引用的对象可以   如果它们是可变的则被修改),但它们也有特殊的语义   在Java内存模型下。这是使用最终字段   可能保证初始化安全(参见第3.5.2节)   这使得可以自由访问和共享不可变对象   同步。(P32)

背诵不安全的出版物:

public class Holder {
      private int n;
      public Holder(int n) { this.n = n; }
      public void assertSanity() {
         if (n != n) // might be true for other threads.
     }
}

令人惊讶的是,n的值可能被其他线程看作陈旧。但final修饰符可以解决问题。与volatile类似,不是吗? final字段本质上是volatile吗? (可能解释为什么不允许final volatile

3 个答案:

答案 0 :(得分:2)

不,final字段本质上不是volatile

如果是这样的话,那会非常昂贵,因为在大多数情况下,你需要在volatile写入后设置StoreLoad屏障。

final字段可以避免这种情况,因为您有一个可以帮助您的附加约束 - 您知道必须在相应的类或实例对象完全初始化时初始化final个字段

规范可能有点难以阅读(看看JLS的section 17.5),但要记住,就像臭名昭着的JMM因果关系部分一样,要点是正式描述将会是什么大多数人的直觉行为。

至于实施,通常需要两件事:

  1. 确保final字段存储(包括字段为引用时存储在链中的存储)不能与构造函数外部的存储重新排序。如果底层硬件架构具有强大的内存模型(如x86),即使你内置构造函数,这通常也是无操作。

  2. 确保给定线程中的第一个final字段加载不能与该字段所属的相应引用的相同线程中的第一个加载重新排序。这几乎总是一个无操作,因为所有编译器和大多数硬件架构都尊重负载依赖性。

  3. 最后,大多数体系结构的LoadStore和StoreStore障碍的成本要低得多,足以实现final字段。

    ===

    您可以在以下网址中详细了解final字段应如何实现:

    ===

    P.S。即使存在final字段,不安全的发布也是危险的。有关注意事项,请参阅here

答案 1 :(得分:1)

  

是否可能导致私有易变?

允许

private volatile

你的意思是final volatile?是的,这些修饰符的性质不兼容 - final var,其值/引用无法更改,不需要额外的volatile条款(以及相关的开销),因为final字段的变异是不可能,跨多个线程的读取是一致的

但是JMM确实为final字段提供了初始化volatile样式的一致性。 AFAIK已在JSR 133中实施(包含在Java SE 5.0中)。在此数据竞争期间JSR init读取可能不一致(例如返回null或某个中间值)

PS:我发现classic article提到了你的问题。强烈推荐它(和第二部分)

答案 2 :(得分:0)

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