Java内存映射文件多线程读/写

4

我有两个线程同时访问同一个大文件(.txt)。

第一个线程从文件读取内容。 第二个线程往文件中写入内容。

两个线程都访问同一个块,例如(start: 0, blocksize: 10),但使用不同的通道和缓冲区实例

读取者:

{
     int BLOCK_SIZE = 10;
     byte[] bytesArr = new byte[BLOCK_SIZE];
     File file = new File("/db.txt");
     RandomAccessFile randomFile = new RandomAccessFile(file, "r");
     FileChannel channel = randomFile.getChannel();
     MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, BLOCK_SIZE);
     map.get(bytesArr , 0, BLOCK_SIZE);
     channel.close();
}

作者:

{
     int BLOCK_SIZE = 10;
     File file = new File("/db.txt");
     RandomAccessFile randomFile = new RandomAccessFile(file, "rw");
     FileChannel channel = randomFile.getChannel();
     MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, BLOCK_SIZE);
     map.put(bytesToWrite);
     channel.close();
}

我知道如果两者同时开始,会出现重叠异常!但我想知道的是,到底在哪个点上发生了重叠?我的意思是,“锁”究竟在什么时候发生? 例如:假设写入者先获得访问权限,那么如果读取者尝试访问,在哪个点上可以实现呢?
 FileChannel channel = randomFile.getChannel();
 // 1- can reader access here?
 MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, BLOCK_SIZE);
 // 2- can reader access here?
 map.put(bytesToWrite);
 // 3- can reader access here?
 channel.close();
 // 4- can reader access here?

1、2、3或4?

第四个不确定,因为通道已关闭!其他几点怎么样?

谢谢!


我在你的代码中没有看到任何锁定。 - Chris K
1
为什么要使用多个线程?提供一些关于您使用情况的概述将有助于我们提供建议。一般情况下,除非发生了非常特殊的情况,否则我建议仅使用一个线程进行I/O。 - Chris K
@ChrisK,我可以给你一个使用案例,但是你熟悉JSF ManagedBeans吗? - Rami.Q
哈哈,完全没有关系,你误读了我的回复(它是开玩笑的,一点也不生气 - 对于造成的困惑表示歉意)。请详细说明你正在解决的问题及其背景。 - Chris K
让我们在聊天中继续这个讨论 - Chris K
显示剩余3条评论
2个回答

3
我正在总结与OP的聊天记录,内容涉及编程。OP有一个心理模型(像我们大多数人一样),即一旦线程写入数据结构,该数据结构立即对所有其他线程可见。在使用内存映射文件进行测试时,OP已确认在单个套接字Intel CPU上似乎是正确的。
不幸的是,这并不是真的,并且这是Java可以展示硬件底层行为的一个领域。Java被设计为假定代码是单线程的,并且因此可以进行优化,直到告知为止。这意味着将根据硬件和Hotspot版本(以及Hotspot收集的统计信息)而有所不同。这种复杂性以及在单个套接字Intel CPU上运行使OP的测试无效。
有关更多信息,请查看以下链接以深入了解“Java内存模型”。特别是,同步不仅意味着“互斥”;在硬件方面,它还涉及“数据可见性”和“指令排序”。这是单线程代码认为理所当然的两个主题。 不用担心这需要时间才能理解,一开始你可能会感到不知所措。我们都有过这样的感受。Java在隐藏这种复杂性方面做得非常出色,但前提是你遵循这个简单的规则。当一个线程读取或修改共享数据结构时,它必须位于同步块内。也就是说,写线程和读线程都必须如此。显然我在简化,但只要遵循这个规则,程序总是可以正常工作的。只有当你非常深入地了解Java内存模型、内存屏障以及它们与不同硬件的关系时,才会打破它。即使是并发专家也尽可能避免打破这个规则;对于许多低延迟系统来说,单线程通常更简单,速度也出奇地快
直接回答OP的问题。问题中的示例代码没有锁定。没有内存屏障,没有任何并发控制。因此,读取和写入之间的交互行为是未定义的。它们可能起作用,也可能不起作用。它们可能大部分时间都能正常工作。Intel具有所有CPU中最强的内存保证,但在单个套接字Intel CPU上运行测试用例会错过许多复杂的错误。Sun也在Java 5和JSR 133发布之前被这个问题困扰(详见有关Java中双重检查锁定为什么失效的文章)。

非常感谢您的大力帮助。您发布的有关Java内存模型和内存屏障的链接非常有用。尽管我当时询问的是关于从/到MappedByteBuffers读取/写入相同块(部分)字节的并发问题,而所有建议/答案都朝着另一个方向发展(可能是由于我的问题表述不清),但这使我意识到我需要更多地了解JVM、内存、系统和硬件之间的交互。 - Rami.Q
这个答案真的有效吗?据我所知,内存映射文件是特殊的。例如,在 POSIX 系统上,它们由操作系统处理,如 mmap。它们保证了一些特殊的行为。例如,如果您更改一个位,将生成页面错误,并且操作系统会将此页面从磁盘交换到内存中,反之亦然。 - slowjack2k

1
这段代码不会出现任何锁定异常,也不会有任何阻塞。文件锁定是在进程之间进行的,而不是在线程之间进行的。你需要在这里使用同步、信号量或读写锁。而且没有必要使用两个通道。

谢谢你的回答,但是能给我举一个重叠发生的用例吗? - Rami.Q
@Rami.Q 你所说的overlapping是什么意思?如果在并发代码中没有使用任何形式的内存屏障,你将不知道读线程中会出现什么。它可能是已经写入的数据,也可能是写入之前的数据,或者是部分写入的数据,因此处于损坏状态。 - Chris K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接