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

C#和Java都定义了这一点
* volatile读取具有获取语义
* volatile写入具有发布语义

我的问题是:

  1. 这是定义volatile的唯一正确方法吗?
  2. 如果没有,如果语义被颠倒,事情就会大不相同,也就是说
    • volatile读取具有释放语义
    • volatile写入具有获取语义

volatile语义背后的原因源于Java内存模型 ,它是根据操作指定的:

  • 读取和写入变量
  • 锁定和解锁显示器
  • 开始和加入线程

Java内存模型定义了一个名为happen-before的部分排序 用于Java程序中可能发生的操作。 通常无法保证线程可以看到每个其他操作的结果。

假设你有两个动作AB. 为了保证执行动作B的线程可以看到动作A的结果,A和B之间必须存在先发生关系。否则,JVM可以随意对它们进行重新排序

未正确同步的程序可能会有数据争用。 当一个变量被> 1个线程读取并由> = 1个线程写入时,会发生数据争用,但读取和写入操作不会通过发生前的排序进行排序。

因此,正确同步的程序没有数据争用,程序内的所有操作都按固定顺序发生。

所以行动通常只是部分订购,但也有一个订单:

  • 锁定获取和释放
  • 读取和写入volatile变量

这些行动是完全有序的。

这使得在“后续”锁定获取和易失性变量读取方面描述发生之前是明智的。

关于你的问题:

  1. 通过before-before关系,您可以使用volatile的替代定义
  2. 颠倒顺序对上面的定义没有意义,特别是因为涉及总订单。

之前发生

这说明了当两个线程使用公共同步时发生在之前的关系。 线程A中的所有操作都按程序顺序规则排序 ,线程B中的操作也是如此。因为A释放锁M和B 随后获取M,因此在释放锁之前A中的所有操作都在B中的操作之前排序获得锁定后。 当两个线程在不同的锁上同步时,我们不能说它们之间的动作顺序,在两个线程中的动作之间没有发生 – 之前的关系。

来源: 实践中的Java并发

获取/释放语义的强大之处不在于其他线程多久能够看到易失性字段本身的新写入值,而在于易失性操作在不同线程之间建立先发生关系的方式。 如果线程A读取volatile字段并且看到在另一个线程B中写入该字段的值,那么线程A也保证在线程B之前看到线程B写入其他 (不一定是易失性)变量的值。易失写。 这看起来像缓存刷新,但仅从读取volatile的线程的角度来看,其他不接触volatile字段的线程没有关于B的排序保证,并且可能会看到一些早期的非易失性写入但是如果编译器/ JIT如此倾向,则不是其他人。

监视器获取/释放的特征类似于它们引发的先发生关系 – 在监视器释放之前,一个线程的动作在另一个线程后续获取同一监视器之后保证可见。 Volatiles为您提供与监视器同步相同的排序保证,但不会阻塞。