C# 和 Java 都定义
* volatile 读取具有获取语义
* volatile 写入具有释放语义
我的问题是:
- 这是定义 volatile 的唯一正确方法吗?
- 如果不是,如果语义颠倒,事情会变得非常不同,也就是说
- 易失性读取具有释放语义
- 易失性写入具有获取语义
C# 和 Java 都定义
* volatile 读取具有获取语义
* volatile 写入具有释放语义
我的问题是:
- 这是定义 volatile 的唯一正确方法吗?
- 如果不是,如果语义颠倒,事情会变得非常不同,也就是说
- 易失性读取具有释放语义
- 易失性写入具有获取语义
语义背后的推理volatile
植根于Java 内存模型,它是根据操作指定的:
Java 内存模型为 Java 程序中可能发生的动作定义了一个称为发生前的部分排序。通常不能保证线程可以看到彼此动作的结果。
假设您有两个动作A和B。为了保证执行动作 B 的线程可以看到动作 A 的结果,A 和 B 之间必须存在先发生关系。如果没有,JVM 可以随意重新排序它们。
未正确同步的程序可能存在数据争用。当一个变量被> 1个线程读取并被> = 1个线程写入时,就会发生数据竞争,但读取和写入操作未通过happens-before排序进行排序。
因此,正确同步的程序没有数据竞争,并且程序中的所有操作都以固定的顺序发生。
所以动作一般只有部分排序,但也有一个全序:
这些动作是完全有序的。
这使得用“后续”锁定获取和易失性变量读取来描述发生之前是明智的。
关于你的问题:
volatile
这说明了当两个线程使用公共锁同步时的发生前关系。线程 A 中的所有动作都按照程序顺序规则排序,线程 B 中的动作也是如此。因为 A 释放锁 M 并且 B随后获取 M,所以在释放锁之前 A 中的所有动作都排序在 B 中的动作之前获得锁后。当两个线程在不同的锁上同步时,我们不能说它们之间的动作顺序,两个线程中的动作之间没有发生之前的关系。
资料来源:Java 并发实践
获取/释放语义的强大之处与其说是其他线程多久能看到 volatile 字段本身的新写入值,不如说是 volatile 操作在不同线程之间建立了先发生关系的方式。如果线程 A 读取一个 volatile 字段并看到另一个线程 B 中写入该字段的值,则线程 A 也可以保证看到线程 B 在执行该操作之前看到由线程 B 写入其他(不一定是 volatile)变量的值易失性写入。这看起来像缓存刷新,但只是从读取 volatile 的线程的角度来看,其他不接触 volatile 字段的线程对 B 没有排序保证,并且如果编译器/JIT 可能会看到其早期的一些非易失性写入,但不会看到其他写入如此倾向。
监视器获取/释放的相似特征在于它们诱导的先发生关系 - 一个线程在释放监视器之前执行的操作保证在另一个线程随后获取同一监视器之后可见。Volatile 为您提供与监视器同步相同的排序保证,但不会阻塞。