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

Posted

技术标签:

【中文标题】java内存映射文件多线程读/写【英文标题】:java Memory mapped Files multithreading read / write 【发布时间】:2015-07-16 11:39:39 【问题描述】:

我有 2 个线程同时访问同一个大文件 (.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?

没有4是肯定的,因为通道被关闭了!其他点呢?

谢谢!

【问题讨论】:

我看到你的代码没有锁定。 为什么要使用多个线程?您的用例的一些概述将有助于我们提供建议。一般来说,除非发生了非常特殊的情况,否则我建议只使用一个线程进行 I/O。 @ChrisK,我可以给你一个用例,但是你熟悉 JSF ManagedBeans 吗? 从我遥远的过去,是的。不过要温柔,我很容易瘀伤。 @ChrisK,对不起,我说了什么让你生气了? 【参考方案1】:

我正在从与 OP 的聊天对话中总结一些笔记。 OP 有这样的心理模型(就像我们大多数人一样),一旦线程写入数据结构,该数据结构就会立即对所有其他线程可见。在使用内存映射文件的 OP 测试中,他确认这在单插槽 Intel CPU 上似乎是正确的。

不幸的是,这不是真的,Java 可以并且确实展示了硬件的底层行为。 Java 被设计为假定代码是单线程的,因此可以对其进行优化,直到另有说明为止。这意味着硬件和热点版本(以及热点收集的统计信息)会有所不同。这种复杂性以及在单插槽 Intel CPU 上运行使 OP 测试无效。

如需更多信息,以下链接将有助于更深入地了解“Java 内存模型”。尤其是同步不仅仅意味着“相互排斥”;在硬件方面,它也与“数据可见性”和“指令排序”有关。单线程代码认为理所当然的两个主题。

The Java Memory Model Double Checked Locking is Broken JSR 133

如果这需要时间来理解,并且一开始您会感到不知所措,请不要担心。一开始我们都这么觉得。当且仅当您遵循这一简单规则时,Java 在隐藏这种复杂性方面做得非常出色。当线程读取或修改共享数据结构时,它必须位于同步块内。即,写入线程和读取线程。显然我正在简化,但遵循该规则,程序将始终有效。仅当您对 Java 内存模型、内存障碍以及它与不同硬件的关系有非常深入的了解时才打破它(即使这样,并发专家也尽可能避免打破该规则;使用单线程通常要简单得多,并且可以是surprisingly fast.. 由于这个原因,许多低延迟系统主要设计为单线程)。


直接回答 OP 的问题。问题中的示例代码没有锁定。没有内存屏障,根本没有并发控制。因此,读取和写入如何交互的行为是未定义的。他们可能会工作,他们可能不会。他们可能大部分时间都在工作。 Intel 拥有所有 CPU 中最强的内存保证,在单插槽 Intel CPU 上运行测试用例会漏掉很多复杂的 bug。在 Java 5 和 JSR 133 出来之前,Sun 也被这件事抓住了(阅读有关为什么在 Java 中破坏双重检查锁定的文章以了解更多详细信息)。

【讨论】:

非常感谢您的大力帮助。您发布的有关 Java 内存模型和内存屏障的链接非常有帮助。尽管我问的是关于从/向 MappedByteBuffers 读取/写入相同的字节块(部分)以及所有建议/答案都朝着另一个方向发展(可能是由于我措辞不好的问题)的并发性,但这让我事实上,我必须阅读更多关于 JVM、内存、系统和硬件之间的交互。 这个答案真的有效吗?据我所知,内存映射文件很特殊。它们由操作系统处理,例如 posix 系统上的 mmap。他们保证一些特殊的行为。例如,如果您稍作更改,则会生成页面错误,并且操作系统会将此页面从磁盘交换到内存,反之亦然。【参考方案2】:

您不会从此代码或任何块中获得任何锁定异常。文件锁在进程之间而不是线程之间运行。您需要的是同步、信号量或 ReadWriteLocks。而且不需要使用两个频道。

【讨论】:

感谢您的回答,但您能给我一个发生重叠的用例吗? @Rami.Q 你说的重叠是什么意思?通过在并发代码中不使用任何形式的内存屏障,您将不知道在读取线程中会看到什么。可能是写入的数据,也可能是写入之前的数据,也可能是部分写入的数据,因此处于损坏状态。

以上是关于java内存映射文件多线程读/写的主要内容,如果未能解决你的问题,请参考以下文章

67.文件映射为内存进行操作与多线程 以及 文件映射到内存根据索引进行内存来实现读取多线程操作

Java内存分配(直接内存堆内存Unsafel类内存映射文件)

内存映射文件的性能特点

JavaNIO的深入研究4内存映射文件I/O,大文件读写操作,Java nio之MappedByteBuffer,高效文件/内存映射

截断内存映射文件

c 共享内存demo