当前位置: 首页 > 知识库问答 >
问题:

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

宗政和韵
2023-03-14

从实践中的Java并发性来看:

当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,对它的操作不应该与其他内存操作一起重新排序。易失性变量不缓存在寄存器或缓存中,在这些寄存器或缓存中,它们对其他处理器隐藏,因此读取易失性变量总是返回任何线程最近的写入。(第25页)

Final字段不能修改(尽管它们引用的对象可以修改,如果它们是可变的),但它们在Java内存模型下也有特殊的语义。使用final字段可以保证初始化安全(参见第3.5.2节),从而可以在不同步的情况下自由访问和共享不可变对象。(第32页)

背诵不安全出版物:

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的值,令人惊讶的是可能会被其他线程看到过时。但是最终修饰符会起到作用。类似于易失性,不是吗?最终字段本质上是易失性吗?(不允许最终易失性的可能解释)

共有3个答案

殷学
2023-03-14

volatile只与变量本身的修改相关,而与它所指的对象无关。因为不能修改字段,所以使用最终易失性字段是没有意义的。只需声明字段final,就可以了。

充星腾
2023-03-14

这是否可能是不允许使用私有volatile的原因?

私有易失性是允许的。

你是说最终易失性吗?是的,这些修饰符本质上是不兼容的-最终var,不能更改值/引用,不需要额外的易失性好东西(和相关的开销),因为最终字段的突变是不可能的,并且跨多个线程的读取是一致的

但是JMM确实为最终字段提供了初始化易失性风格的一致性。AFAIK它在JSR 133中实现(包含在JavaSE 5.0中)。在此之前,JSR初始化读取在数据竞争期间可能不一致(例如返回null或一些中间值)

附言:我找到了一篇提到你问题的经典文章。强烈推荐它(和第二部分)

颜安宁
2023-03-14

不,最终字段本质上不是易失性

如果是的话,这将是不必要的昂贵,因为在大多数情况下,您需要在volatile写入之后设置StoreLoad屏障。

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

该规范可能有点难以阅读(请参阅JLS的第17.5节),但请记住,与臭名昭著的JMM因果关系部分一样,主要内容是正式描述大多数人的直觉行为。

至于实现,通常需要2件事:

>

  • 确保最终字段存储,包括如果字段是引用的链下存储,不能与构造函数之外的存储重新排序。这通常是不操作的,即使您内联您的构造函数,如果底层硬件架构具有强大的内存模型(如x86)。

    确保给定线程中的第一个最终字段加载不能与该字段所属的相应引用的同一线程中的第一个加载重新排序。这几乎总是不可行的,因为所有编译器和大多数硬件体系结构都遵循负载依赖性。

    最后,大多数体系结构上成本低得多的LoadStore和StoreStore屏障应该足以实现最终字段。

    ===

    有关如何实现最终字段的更多信息,请参见:

    • Doug Lea的JMM食谱(见最后的Fields部分)
    • 阿列克西·希皮列夫(AlexeyShipilev)的《所有领域都很棒》是最后一篇博文

    ===

    P、 即使存在最终字段,不安全的出版物也是危险的。请参见此处了解一些注意事项。

  •  类似资料:
    • 我试图理解java volatile的本质及其语义,以及它对底层架构和指令的转换。如果我们考虑以下博客和资源 生成的栅栏的易失性,什么得到生成的读/写的易失性和堆栈溢出问题上的栅栏 以下是我收集的信息: volatile read在其后面插入loadStore/LoadLoad屏障(x86上的LFENCE指令) 它可以防止在后续写入/加载时对加载进行重新排序 它应该保证加载由其他线程修改的全局状态

    • 本文向大家介绍讲一下volatile涉及的Java内存模型?相关面试题,主要包含被问及讲一下volatile涉及的Java内存模型?时的应答技巧和注意事项,需要的朋友参考一下 在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可

    • 问题内容: 当超类具有标记为final的字段但子类覆盖(隐藏?)此字段时,会发生什么?“最终”并没有阻止它,不是吗?我正在处理的特定示例是Building类,从中继承了各种建筑物。除其他事项外,每种类型的成本对于每个子类都应是最终成本,但是每种类型的建筑物应具有自己的成本。 编辑:从那以后,我意识到我不知道上面我在说什么。我真正想要的是成本的静态变量。但是,如果我在超类中声明这些静态变量,则它们对

    • 规范了Java虚拟机与计算机内存是如何协调工作的,规定了一个线程如何及何时能看到其他线程修改过的共享变量,在必须时如何同步地访问共享变量,控制线程本地内容和共享内容之间的同步。 2. 同步八种操作 操作 定义 lock(锁定) unlock(解锁) read(读取) load(载入) use(使用) assign(赋值) store(存储) write(写入) 3. 同步规则 Read和Load之

    • 我们对非最终场有同样的保证吗?换句话说,某些线程是否可能观察到非空的引用,但字段为? 提前道谢!

    • 我读了SO的以下文章 字段读取同步和易失性之间的区别 发问者写道 同步的目的是确保此线程读取的acct.balance值是当前值,并且对acct.balance中对象字段的任何挂起写入也会写入主存。 最受欢迎的答案: 你是正确的。 请研究此代码: 在我的电脑上,这个程序不会终止。 因此我认为 如果我更改volatile变量,我将在另一个线程中看到任何未完成的地方的实际值 我说得对吗?