Java和C#中volatile语义背后的原理是什么?

12

C# 和 Java 都定义了以下规则:
* volatile 读取具有获取语义
* volatile 写入具有释放语义

我的问题是:

  1. 这是唯一正确的定义 volatile 的方式吗?
  2. 如果不是,如果语义被颠倒,会有很大的区别吗?即
    • volatile 读取具有释放语义
    • volatile 写入具有获取语义

3
Java 是在哪里定义了这个?我以前从没听说过。 - Peter Lawrey
9
对于那些主要基于“虚拟机”定义的编程语言而言,像“CPU高速缓存”这样具体的概念会感觉不太适合。 - sarnold
1
有趣的文档:http://g.oswego.edu/dl/jmm/cookbook.html,作者是Doug Lea(Java并发团队)。 - assylias
3
正如@sarnold所暗示的那样,您正在将语义要求与实现细节混淆。恰好,CPU缓存可能需要被刷新以满足volatile读取(尽管这不一定是必须的),但这只是如何强制执行volatile读取定义的细节而已。 - dlev
3
CPU缓存最后一个关键的架构是Alpha。在所有现代的、幸存的使用多个CPU/核心的架构中,CPU缓存都是使用某种MESI协议的硬件一致性。因此,刷新或清空CPU缓存只会降低性能,并且作为同步机制是没有用处的。 - David Schwartz
显示剩余3条评论
2个回答

13

volatile 语义的原因源于Java Memory Model,该模型是以操作为基础进行规定的:

  • 变量的读写操作
  • 监视器的锁定和解锁操作
  • 线程的启动和加入操作

Java Memory Model 为可以在 Java 程序中发生的操作定义了一个称为happens-beforepartial ordering。通常情况下,没有保证线程可以看到彼此操作的结果。

假设您有两个操作AB。为了保证执行操作 B 的线程可以看到操作 A 的结果,必须存在happens-before关系。如果不存在,JVM 可以根据自己的意愿重新排序它们。

一个未正确同步的程序可能会出现数据竞争。当一个变量被大于1个线程读取并被大于等于1个线程写入时,但是读取和写入操作没有通过happens-before排序进行排序时,就会发生数据竞争。
因此,一个正确同步的程序没有数据竞争,并且程序中的所有操作按照固定顺序发生。
因此,操作通常只是部分有序的,但是锁获取和释放以及对volatile变量的读写之间也存在完全顺序。
这些操作是完全有序的。
这使得可以用“随后”锁获取和volatile变量读取来描述happens-before关系。
关于您的问题:
1.通过happens-before关系,您可以对“volatile”进行另一种定义。
2.颠倒顺序不符合上述定义,特别是因为涉及到完全顺序。

happens-before

这里展示了两个线程使用一个共同的进行同步时发生的先后关系。线程A中的所有操作都遵循程序顺序规则排序,线程B中的操作也是如此。由于A释放锁M并且B 随后 获取M,因此在A释放锁之前的所有操作先于在B获取锁之后的操作。当两个线程在不同的锁上进行同步时,我们不能确定它们之间的操作顺序,因为在两个线程中的操作之间没有先后关系。

来源:Java并发实践


5
获得/release语义的威力不在于其他线程多快看到易失性字段本身新写入的值,而是在于易失性操作建立了跨不同线程的happens-before关系。如果线程A读取易失性字段并看到另一个线程B写入该字段的值,则线程A还保证能够看到线程B在进行易失性写入之前写入到其他(不一定是易失性)变量的值。这看起来像缓存刷新,但仅适用于读取易失性的线程,在处理B时,不接触易失性字段的其他线程没有任何排序保证,并且如果编译器/JIT倾向于如此,可能会看到其先前的某些非易失性写入但不是全部。
监视器获得/释放同样通过他们诱导的happens-before关系来描述——一个线程在释放监视器之前的操作保证在另一个线程获取相同监视器之后可见。易失性提供与监视器同样的排序保证,但无需阻塞。

这是一个关于 volatile 的获取和释放语义的好解释。但为什么线程 A 还必须看到线程 B 的所有非 volatile 写操作呢? - yash
2
这篇文章提供了一个很好的解释。基本上,它使得 volatile 变量更加有用,因为它们可以用于设置一个 volatile 标志来表示某个操作已经完成 - 另一个线程读取该标志时可以知道该操作已经完成,而不是可能看到一些部分完成的中间状态。 - Ian Roberts
很棒的答案;我正在慢慢开始学习更多关于内存模型的知识,这有助于巩固我的理解。 - anton.burger

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