JSR-133 说明书如何执行 Java 内存模型做出的所有保证
How does the JSR-133 cookbook enforce all the guarantees made by the Java Memory Model
我的理解是,JSR-133 cookbook 是关于如何使用一系列内存屏障(或至少是可见性保证)实现 Java 内存模型的引述很好的指南。
根据对不同类型障碍的描述,这也是我的理解,StoreLoad 是唯一一个保证所有 CPU 缓冲区都刷新到缓存并因此确保新鲜读取(通过避免存储转发) 并保证由于缓存一致性而观察到最新值。
我正在查看 table volatile/regular stores/loads 的不同程序顺序交错所需的特定障碍,以及需要哪些内存障碍。
根据我的直觉,这个 table 似乎不完整。例如,Java 内存模型保证监视器的获取操作对在另一个线程中释放之前执行的所有操作的可见性,即使正在更新的值是非易失性的。在上面 link 中的 table 中,似乎唯一刷新 CPU 缓冲区并传播 changes/allow 要观察到的新更改的操作是随后的 Volatile Store 或 MonitorExit通过易失性负载或 MonitorEnter。在我上面的示例中,当这些操作(根据 table)仅使用 LoadStore 和 StoreStore 时,我看不出障碍如何保证可见性,根据我的理解,它们仅与线程中的重新排序有关,而不能在保证之前强制执行发生(跨线程)。
我哪里理解错了?或者此实现是否仅在 acquiring/releasing 监视器上强制执行,而不是同步保证或额外操作。
谢谢
文档中的障碍是抽象概念,或多或少映射到不同 CPU 上的不同事物。但它们只是指导方针。 JVM 实际上 必须遵循的规则是 JLS 第 17 章中的规则。
障碍作为一个概念也是 "global",因为它们对 所有 的指令进行排序。
For example, the Java memory model guarantees visibility on the acquire action of a monitor to all actions performed before it's release in another thread, even if the values being updated are non volatile.
获取监视器是cookbook中的monitor-enter,只需要对其他争用锁的线程可见即可。 monitor-exit 是释放操作,它将防止在它之前的加载和存储从它下面移动。您可以在食谱表中看到这一点,其中第一个操作是正常的 load/store,第二个是 volatile-store 或 monitor-exit。
在具有 Total Store Order 的 CPU 上,存储缓冲区(如果可用)对正确性没有影响;只看性能。
在任何情况下,由 JVM 使用提供 JLS 所需的原子性和可见性语义的指令。这就是关键要点:如果您编写 Java 代码,您将针对 JLS 中定义的抽象机进行编码。如果仅对抽象机器编码不能提供您需要的性能,那么您只会深入研究具体机器的实现细节。你不需要为了正确性去那里。
StoreLoad is the only one that guarantees all CPU buffers are flushed to cache and therefore ensure fresh reads (by avoiding store forwarding) and guarantees the observation of the latest value due to cache coherency.
这对于 x86 体系结构可能是正确的,但您不应该考虑那种抽象级别。高速缓存一致性对于要执行的处理器来说可能代价高昂。
以移动设备为例,一个重要的目标是减少程序消耗的电池电量。在那种情况下,它们可能不会参与缓存一致性并且 StoreLoad
失去此功能。
I don't see how the barriers could guarantee visibility in my above example, when those operations (according to the table) only use LoadStore and StoreStore which from my understanding are only concerned with re-ordering in a thread and cannot enforce the happens before guarantee (across threads that is).
让我们只考虑一个可变字段。不稳定的加载和存储看起来如何?好吧,Aleksey Shipilëv has a great write up on this,但我会分一杯羹。
一个易失性存储然后随后的加载看起来像:
<other ops>
[StoreStore]
[LoadStore]
x = 1; // volatile store
[StoreLoad] // Case (a): Guard after volatile stores
...
[StoreLoad] // Case (b): Guard before volatile loads
int t = x; // volatile load
[LoadLoad]
[LoadStore]
<other ops>
因此,<other ops>
可以是非易失性写入,但如您所见,这些写入是在易失性存储之前提交到内存中的。然后当我们准备好读取 LoadLoad
时,LoadStore
将强制等待,直到 volatile store
成功。
最后,StoreLoad
before 和 after 确保易失性加载和存储不会被重新排序,如果紧接在另一个之前。
我不确定你从哪里得知 StoreLoad
障碍是强制执行某些特定行为的唯一类型。抽象地说,所有障碍都严格执行它们定义为要执行的操作。例如,LoadLoad
防止任何先前的加载与任何后续加载重新排序。
可能有体系结构特定描述如何实施特定屏障:例如,在 x86 上,除 StoreLoad 之外的所有屏障是空操作,因为芯片架构会自动执行其他排序,并且 StoreLoad
通常实现为存储缓冲区刷新。尽管如此,所有障碍都有其独立于体系结构的抽象定义,说明书是根据它定义的,以及概念障碍到实际 ISA 特定实现的映射。
特别是,即使屏障在特定平台上是 "no-op",这也意味着顺序得以保留,因此满足了所有 happens-before 和其他同步要求。
我的理解是,JSR-133 cookbook 是关于如何使用一系列内存屏障(或至少是可见性保证)实现 Java 内存模型的引述很好的指南。
根据对不同类型障碍的描述,这也是我的理解,StoreLoad 是唯一一个保证所有 CPU 缓冲区都刷新到缓存并因此确保新鲜读取(通过避免存储转发) 并保证由于缓存一致性而观察到最新值。
我正在查看 table volatile/regular stores/loads 的不同程序顺序交错所需的特定障碍,以及需要哪些内存障碍。
根据我的直觉,这个 table 似乎不完整。例如,Java 内存模型保证监视器的获取操作对在另一个线程中释放之前执行的所有操作的可见性,即使正在更新的值是非易失性的。在上面 link 中的 table 中,似乎唯一刷新 CPU 缓冲区并传播 changes/allow 要观察到的新更改的操作是随后的 Volatile Store 或 MonitorExit通过易失性负载或 MonitorEnter。在我上面的示例中,当这些操作(根据 table)仅使用 LoadStore 和 StoreStore 时,我看不出障碍如何保证可见性,根据我的理解,它们仅与线程中的重新排序有关,而不能在保证之前强制执行发生(跨线程)。
我哪里理解错了?或者此实现是否仅在 acquiring/releasing 监视器上强制执行,而不是同步保证或额外操作。
谢谢
文档中的障碍是抽象概念,或多或少映射到不同 CPU 上的不同事物。但它们只是指导方针。 JVM 实际上 必须遵循的规则是 JLS 第 17 章中的规则。
障碍作为一个概念也是 "global",因为它们对 所有 的指令进行排序。
For example, the Java memory model guarantees visibility on the acquire action of a monitor to all actions performed before it's release in another thread, even if the values being updated are non volatile.
获取监视器是cookbook中的monitor-enter,只需要对其他争用锁的线程可见即可。 monitor-exit 是释放操作,它将防止在它之前的加载和存储从它下面移动。您可以在食谱表中看到这一点,其中第一个操作是正常的 load/store,第二个是 volatile-store 或 monitor-exit。
在具有 Total Store Order 的 CPU 上,存储缓冲区(如果可用)对正确性没有影响;只看性能。
在任何情况下,由 JVM 使用提供 JLS 所需的原子性和可见性语义的指令。这就是关键要点:如果您编写 Java 代码,您将针对 JLS 中定义的抽象机进行编码。如果仅对抽象机器编码不能提供您需要的性能,那么您只会深入研究具体机器的实现细节。你不需要为了正确性去那里。
StoreLoad is the only one that guarantees all CPU buffers are flushed to cache and therefore ensure fresh reads (by avoiding store forwarding) and guarantees the observation of the latest value due to cache coherency.
这对于 x86 体系结构可能是正确的,但您不应该考虑那种抽象级别。高速缓存一致性对于要执行的处理器来说可能代价高昂。
以移动设备为例,一个重要的目标是减少程序消耗的电池电量。在那种情况下,它们可能不会参与缓存一致性并且 StoreLoad
失去此功能。
I don't see how the barriers could guarantee visibility in my above example, when those operations (according to the table) only use LoadStore and StoreStore which from my understanding are only concerned with re-ordering in a thread and cannot enforce the happens before guarantee (across threads that is).
让我们只考虑一个可变字段。不稳定的加载和存储看起来如何?好吧,Aleksey Shipilëv has a great write up on this,但我会分一杯羹。
一个易失性存储然后随后的加载看起来像:
<other ops>
[StoreStore]
[LoadStore]
x = 1; // volatile store
[StoreLoad] // Case (a): Guard after volatile stores
...
[StoreLoad] // Case (b): Guard before volatile loads
int t = x; // volatile load
[LoadLoad]
[LoadStore]
<other ops>
因此,<other ops>
可以是非易失性写入,但如您所见,这些写入是在易失性存储之前提交到内存中的。然后当我们准备好读取 LoadLoad
时,LoadStore
将强制等待,直到 volatile store
成功。
最后,StoreLoad
before 和 after 确保易失性加载和存储不会被重新排序,如果紧接在另一个之前。
我不确定你从哪里得知 StoreLoad
障碍是强制执行某些特定行为的唯一类型。抽象地说,所有障碍都严格执行它们定义为要执行的操作。例如,LoadLoad
防止任何先前的加载与任何后续加载重新排序。
可能有体系结构特定描述如何实施特定屏障:例如,在 x86 上,除 StoreLoad 之外的所有屏障是空操作,因为芯片架构会自动执行其他排序,并且 StoreLoad
通常实现为存储缓冲区刷新。尽管如此,所有障碍都有其独立于体系结构的抽象定义,说明书是根据它定义的,以及概念障碍到实际 ISA 特定实现的映射。
特别是,即使屏障在特定平台上是 "no-op",这也意味着顺序得以保留,因此满足了所有 happens-before 和其他同步要求。