直接 ByteBuffer 之前发生
Happens-before for direct ByteBuffer
我在一个线程中有一个直接的 ByteBuffer(堆外),并使用 JMM 给我的一种机制将它安全地发布到另一个线程。 happens-before 关系是否扩展到由 ByteBuffer 包装的本机(堆外)内存?如果不是,我如何安全地将直接 ByteBuffer 的内容从一个线程发布到另一个线程?
编辑
这不是 Can multiple threads see writes on a direct mapped ByteBuffer in Java? 的副本,因为
- 我说的不是 mmaped() 区域,而是一般的堆外区域
- 我正在安全地发布 ByteBuffer
- 我没有同时修改 ByteBuffer 的内容,我只是将它从一个线程转移到另一个线程
编辑 2
这不是 Options to make Java's ByteBuffer thread safe 的副本我不是要同时修改来自两个不同线程的 ByteBuffer。我试图将 if 从一个线程移交给另一个线程,并在由直接 ByteBuffer 支持的本机内存区域上获得 happens-before 语义。第一个线程在移交后将不再修改或读取 ByteBuffer。
Java 对象监视器的 happens-before 顺序语义在 §17.4.5 中描述为:
The wait
methods of class Object
(§17.2.1) have lock and unlock actions associated with them; their happens-before relationships are defined by these associated actions.
未指定这仅适用于 Java 管理的对象还是适用于任何数据。毕竟,Java 并不关心 Java "world" 之外发生的事情。但这也意味着我们可以将规范外推到 Java 世界中的任何数据 reachable。那么与堆的关系就变得不那么重要了。毕竟,如果我同步线程,为什么它不能直接用于 ByteBuffer?
为了确认这一点,我们可以看一下它在 OpenJDK 中的实际实现方式。
如果我们仔细观察,我们会发现 ObjectMonitor::wait
,除此之外 does:
OrderAccess::fence();
和ObjectMonitor::exit
(notify
/notifyAll
的业务结束)does:
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::storeload() ;
fence()
和 storeload()
都会导致全局 StoreLoad 内存栅栏:
inline void OrderAccess::storeload() { fence(); }
在 SPARC 上它生成 membar
指令:
__asm__ volatile ("membar #StoreLoad" : : :);
在 x86 上它是 to membar(Assembler::StoreLoad)
and subsequently:
// Serializes memory and blows flags
void membar(Membar_mask_bits order_constraint) {
if (os::is_MP()) {
// We only have to handle StoreLoad
if (order_constraint & StoreLoad) {
// All usable chips support "locked" instructions which suffice
// as barriers, and are much faster than the alternative of
// using cpuid instruction. We use here a locked add [esp],0.
// This is conveniently otherwise a no-op except for blowing
// flags.
// Any change to this code may need to revisit other places in
// the code where this idiom is used, in particular the
// orderAccess code.
lock();
addl(Address(rsp, 0), 0);// Assert the lock# signal here
}
}
}
就是这样,它只是 CPU 级别的内存障碍。引用计数和垃圾收集在更高级别发挥作用。
这意味着至少在 OpenJDK 中,在 Object.notify
之前发出的 任何 内存写入将在 在 发出的任何读取之前排序在 Object.wait
.
之后
当然,如果您在 Java 代码中读写 ByteBuffer
,使用 Java 方法,例如 put
和 get
,那么会发生 -在第一个线程上的修改 publishing/consumption 与第二个线程上的最终后续访问之间的关系之前,将以预期的方式应用 0。毕竟 ByteBuffer
由 "off heap" 内存支持这一事实只是一个实现细节:它不允许 ByteBuffer
上的 Java 方法破坏内存模型契约.
如果您谈论从通过 JNI 或其他机制调用的 本机代码 写入此字节缓冲区,事情就会变得有点模糊。我认为只要您在本机代码中使用普通存储(即,不是非临时存储或任何语义比普通存储弱的东西),您在实践中就会很好。毕竟 JMV 在内部通过相同的机制实现了对堆内存的存储,特别是 get
和 put
类型的方法将通过正常的加载和存储来实现。发布操作通常涉及某种类型的发布存储,将应用于所有先前的 Java 操作以及您本机代码中的存储。
您可以在或多或少涉及此主题的并发邮件列表中找到一些 expert discussion。确切的问题是 "Can I use Java locks to protect a buffer accessed only by native code",但潜在的问题几乎相同。结论似乎与上面一致:如果你正常加载和存储到一个正常的1内存区域是安全的。如果你想使用较弱的指令,你需要一个栅栏。
0 所以这是一个有点冗长、折磨人的句子,但我想说清楚的是,有一整条 happens-before 对必须是正确同步以使其工作:(A) 在写入缓冲区和第一个线程上的发布存储之间,(B) 发布存储和消耗负载 (C) 消耗负载和第二个线程的后续读取或写入线。对 (B) 完全在 Java-land 中,因此遵循常规规则。那么问题主要是关于具有一个 "native" 元素的 (A) 和 (C) 是否也可以。
1 Normal 在这种情况下或多或少意味着 Java 使用的相同类型的内存区域,或者在至少一个在内存类型 Java 使用方面具有很强的一致性保证。你必须竭尽全力违反这一点,因为你正在使用 ByteBuffer
你已经知道该区域是由 Java 分配的并且必须按照正常规则进行游戏(因为 [=47= ByteBuffer
上的 ] 级方法至少需要以与内存模型一致的方式工作。
我在一个线程中有一个直接的 ByteBuffer(堆外),并使用 JMM 给我的一种机制将它安全地发布到另一个线程。 happens-before 关系是否扩展到由 ByteBuffer 包装的本机(堆外)内存?如果不是,我如何安全地将直接 ByteBuffer 的内容从一个线程发布到另一个线程?
编辑
这不是 Can multiple threads see writes on a direct mapped ByteBuffer in Java? 的副本,因为
- 我说的不是 mmaped() 区域,而是一般的堆外区域
- 我正在安全地发布 ByteBuffer
- 我没有同时修改 ByteBuffer 的内容,我只是将它从一个线程转移到另一个线程
编辑 2
这不是 Options to make Java's ByteBuffer thread safe 的副本我不是要同时修改来自两个不同线程的 ByteBuffer。我试图将 if 从一个线程移交给另一个线程,并在由直接 ByteBuffer 支持的本机内存区域上获得 happens-before 语义。第一个线程在移交后将不再修改或读取 ByteBuffer。
Java 对象监视器的 happens-before 顺序语义在 §17.4.5 中描述为:
The
wait
methods of classObject
(§17.2.1) have lock and unlock actions associated with them; their happens-before relationships are defined by these associated actions.
未指定这仅适用于 Java 管理的对象还是适用于任何数据。毕竟,Java 并不关心 Java "world" 之外发生的事情。但这也意味着我们可以将规范外推到 Java 世界中的任何数据 reachable。那么与堆的关系就变得不那么重要了。毕竟,如果我同步线程,为什么它不能直接用于 ByteBuffer?
为了确认这一点,我们可以看一下它在 OpenJDK 中的实际实现方式。
如果我们仔细观察,我们会发现 ObjectMonitor::wait
,除此之外 does:
OrderAccess::fence();
和ObjectMonitor::exit
(notify
/notifyAll
的业务结束)does:
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::storeload() ;
fence()
和 storeload()
都会导致全局 StoreLoad 内存栅栏:
inline void OrderAccess::storeload() { fence(); }
在 SPARC 上它生成 membar
指令:
__asm__ volatile ("membar #StoreLoad" : : :);
在 x86 上它是 to membar(Assembler::StoreLoad)
and subsequently:
// Serializes memory and blows flags
void membar(Membar_mask_bits order_constraint) {
if (os::is_MP()) {
// We only have to handle StoreLoad
if (order_constraint & StoreLoad) {
// All usable chips support "locked" instructions which suffice
// as barriers, and are much faster than the alternative of
// using cpuid instruction. We use here a locked add [esp],0.
// This is conveniently otherwise a no-op except for blowing
// flags.
// Any change to this code may need to revisit other places in
// the code where this idiom is used, in particular the
// orderAccess code.
lock();
addl(Address(rsp, 0), 0);// Assert the lock# signal here
}
}
}
就是这样,它只是 CPU 级别的内存障碍。引用计数和垃圾收集在更高级别发挥作用。
这意味着至少在 OpenJDK 中,在 Object.notify
之前发出的 任何 内存写入将在 在 发出的任何读取之前排序在 Object.wait
.
当然,如果您在 Java 代码中读写 ByteBuffer
,使用 Java 方法,例如 put
和 get
,那么会发生 -在第一个线程上的修改 publishing/consumption 与第二个线程上的最终后续访问之间的关系之前,将以预期的方式应用 0。毕竟 ByteBuffer
由 "off heap" 内存支持这一事实只是一个实现细节:它不允许 ByteBuffer
上的 Java 方法破坏内存模型契约.
如果您谈论从通过 JNI 或其他机制调用的 本机代码 写入此字节缓冲区,事情就会变得有点模糊。我认为只要您在本机代码中使用普通存储(即,不是非临时存储或任何语义比普通存储弱的东西),您在实践中就会很好。毕竟 JMV 在内部通过相同的机制实现了对堆内存的存储,特别是 get
和 put
类型的方法将通过正常的加载和存储来实现。发布操作通常涉及某种类型的发布存储,将应用于所有先前的 Java 操作以及您本机代码中的存储。
您可以在或多或少涉及此主题的并发邮件列表中找到一些 expert discussion。确切的问题是 "Can I use Java locks to protect a buffer accessed only by native code",但潜在的问题几乎相同。结论似乎与上面一致:如果你正常加载和存储到一个正常的1内存区域是安全的。如果你想使用较弱的指令,你需要一个栅栏。
0 所以这是一个有点冗长、折磨人的句子,但我想说清楚的是,有一整条 happens-before 对必须是正确同步以使其工作:(A) 在写入缓冲区和第一个线程上的发布存储之间,(B) 发布存储和消耗负载 (C) 消耗负载和第二个线程的后续读取或写入线。对 (B) 完全在 Java-land 中,因此遵循常规规则。那么问题主要是关于具有一个 "native" 元素的 (A) 和 (C) 是否也可以。
1 Normal 在这种情况下或多或少意味着 Java 使用的相同类型的内存区域,或者在至少一个在内存类型 Java 使用方面具有很强的一致性保证。你必须竭尽全力违反这一点,因为你正在使用 ByteBuffer
你已经知道该区域是由 Java 分配的并且必须按照正常规则进行游戏(因为 [=47= ByteBuffer
上的 ] 级方法至少需要以与内存模型一致的方式工作。