[英]Can multiple threads see writes on a direct mapped ByteBuffer in Java?
我正在研究使用ByteBuffers從內存映射文件(通過FileChannel.map() )以及內存中直接ByteBuffers構建的東西。 我試圖了解並發和內存模型約束。
我已經閱讀了FileChannel,ByteBuffer,MappedByteBuffer等所有相關的Javadoc(和源代碼)。很明顯,特定的ByteBuffer(和相關的子類)有一堆字段,並且狀態不受內存模型的保護觀點看法。 因此,如果跨線程使用該緩沖區,則必須在修改特定ByteBuffer的狀態時進行同步。 常見的技巧包括使用ThreadLocal包裝ByteBuffer,復制(同步)以獲取指向相同映射字節的新實例等。
鑒於這種情況:
B_all
用於整個文件(比如它<2gb) B_1
,該文件的一大塊並將其提供給線程T1 B_2
並將其提供給線程T2 我的問題是:T1可以寫入B_1,T2可以同時寫入B_2並保證看到彼此的變化嗎? T3可以使用B_all來讀取這些字節並保證看到T1和T2的變化嗎?
我知道,除非使用force()指示操作系統將頁面寫入磁盤,否則不必在進程中看到映射文件中的寫入。 我不在乎。 假設這個問題,這個JVM是編寫單個映射文件的唯一進程。
注意:我不是在尋找猜測(我可以自己做得很好)。 我想引用一些關於內存映射直接緩沖區保證(或不保證)的內容。 或者,如果您有實際經驗或負面測試用例,那么這也可以作為充分的證據。
更新:我已經完成了一些測試,讓多個線程並行寫入同一個文件,到目前為止,似乎這些寫入可以立即從其他線程中看到。 我不確定我是否可以依靠它。
使用JVM的內存映射只是CreateFileMapping(Windows)或mmap(posix)的一個薄包裝。 因此,您可以直接訪問OS的緩沖區緩存。 這意味着這些緩沖區是操作系統認為要包含的文件(操作系統最終會同步文件以反映這一點)。
因此,無需調用force()來在進程之間進行同步。 這些進程已經同步(通過操作系統 - 甚至讀/寫訪問相同的頁面)。 強制只在操作系統和驅動器控制器之間進行同步(驅動器控制器和物理盤片之間可能存在一些延遲,但是您沒有硬件支持來執行任何操作)。
無論如何,內存映射文件是線程和/或進程之間可接受的共享內存形式。 這個共享內存與Windows中一個命名的虛擬內存塊之間的唯一區別是最終與磁盤同步(事實上,mmap通過映射/ dev / null來完成沒有文件內容的虛擬內存)。
從多個進程/線程讀取寫入內存仍然需要一些同步,因為處理器能夠執行無序執行(不確定它與JVM交互的程度,但是你不能做出假設),而是從中編寫一個字節一個線程通常與寫入堆中的任何字節具有相同的保證。 一旦您寫入它,每個線程和每個進程都將看到更新(即使通過打開/讀取操作)。
有關更多信息,請在posix中查找mmap(或者在Windows中使用CreateFileMapping,它的構建方式幾乎相同。
不可以.JVM內存模型(JMM)不保證多個線程變異(不同步)數據會看到彼此的變化。
首先,鑒於訪問共享內存的所有線程都在同一個JVM中,通過映射的ByteBuffer訪問此內存的事實是無關緊要的(通過ByteBuffer訪問的內存上沒有隱式的volatile或同步),所以問題相當於一個關於訪問字節數組的問題。
讓我們重新解釋這個問題,使其關於字節數組:
- 管理器有一個字節數組:
byte[] B_all
- 創建對該數組的新引用:
byte[] B_1 = B_all
,並給予線程T1
- 創建對該數組的另一個引用:
byte[] B_2 = B_all
,並給予線程T2
通過線程
T1
寫入B_1
是否可以通過線程T2
在B_2
看到?
不,如果沒有T_1
和T_2
之間的明確同步,則無法保證可以看到此類寫入。 問題的核心是JVM的JIT,處理器和內存架構可以自由地重新排序一些內存訪問(不僅僅是為了讓你失望,而是通過緩存來提高性能)。 所有這些層都期望軟件是明確的(通過鎖定,易失性或其他顯式提示)關於需要同步的位置,這意味着當沒有提供這樣的提示時,這些層可以隨意移動。
請注意,實際上,您是否看到寫入主要取決於硬件和各種級別的高速緩存和寄存器中數據的對齊方式,以及運行線程在內存層次結構中的“距離”。
JSR-133是為了精確定義Java 5.0的Java內存模型(據我所知,它在2012年仍然適用)。 這就是你想要尋找確定(雖然密集)答案的地方: http : //www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf (第2節是最相關的)。 更多可讀的東西可以在JMM網頁上找到: http : //www.cs.umd.edu/~pugh/java/memoryModel/
我的部分答案是斷言a ByteBuffer
在數據同步方面與byte[]
沒有什么不同。 我找不到具體說明這一點的文檔,但我建議java.nio.Buffer doc的“Thread Safety”部分會提到有關同步或volatile的內容(如果適用)。 由於文檔沒有提到這一點,我們不應該期望這樣的行為。
您可以做的最便宜的事情是使用volatile變量。 在線程寫入映射區域后,它應該將值寫入volatile變量。 任何讀取線程都應在讀取映射緩沖區之前讀取volatile變量。 這樣做會在Java內存模型中產生“先發生過”。
請注意,您無法保證其他進程正在編寫新內容。 但是如果你想保證其他線程可以看到你寫的東西,寫一個volatile(然后從讀取線程讀取它)就可以了。
我認為直接內存提供與堆內存相同的保證或缺少它們。 如果修改共享底層數組或直接內存地址的ByteBuffer,則第二個ByteBuffer是另一個線程可以看到更改,但不保證這樣做。
我懷疑即使你使用synchronized或volatile,它仍然不能保證工作,但它可能會這樣做取決於平台。
在線程之間更改數據的簡單方法是使用Exchanger
基於這個例子,
class FillAndEmpty {
final Exchanger<ByteBuffer> exchanger = new Exchanger<ByteBuffer>();
ByteBuffer initialEmptyBuffer = ... a made-up type
ByteBuffer initialFullBuffer = ...
class FillingLoop implements Runnable {
public void run() {
ByteBuffer currentBuffer = initialEmptyBuffer;
try {
while (currentBuffer != null) {
addToBuffer(currentBuffer);
if (currentBuffer.remaining() == 0)
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) { ... handle ... }
}
}
class EmptyingLoop implements Runnable {
public void run() {
ByteBuffer currentBuffer = initialFullBuffer;
try {
while (currentBuffer != null) {
takeFromBuffer(currentBuffer);
if (currentBuffer.remaining() == 0)
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) { ... handle ...}
}
}
void start() {
new Thread(new FillingLoop()).start();
new Thread(new EmptyingLoop()).start();
}
}
我不認為這是有保障的。 如果Java內存模型沒有說它是保證它的定義不保證。 對於處理所有寫入的一個線程,我要保護帶有同步或隊列寫入的緩沖區寫入。 后者與多核緩存很好地配合(最好每個RAM位置有1個寫入器)。
不,它與普通的java變量或數組元素沒有什么不同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.