使用Java MappedByteBuffer进行并发读取

时间:2017-05-23 11:00:55

标签: java multithreading concurrency

我正在尝试使用MappedByteBuffer来允许多个线程对文件进行并发读取,但具有以下约束:

  • 文件太大而无法加载到内存中
  • 线程必须能够异步读取(它是一个Web应用程序)
  • 该文件永远不会被任何线程写入
  • 每个线程总是知道它需要读取的确切偏移量和字节长度(即 - 不是由应用程序本身“寻找”)。

根据文档(https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html),缓冲区不是线程安全的,因为它们保持内部状态(位置等)。有没有办法对文件进行并发随机访问而不将其全部加载到内存中?

虽然FileChannel在技术上是线程安全的,但是来自docs:

  

在从现有流或随机访问文件获取文件通道的情况下,文件通道的状态与其getChannel方法返回通道的对象的状态紧密相关。无论是显式地还是通过读取或写入字节来改变通道的位置,都将改变原始对象的文件位置,反之亦然

所以它似乎只是同步。如果我在每个线程中new RandomAccessFile().getChannel().map() [编辑:在每次读取时]那么不会导致MappedByteBuffers应该避免的每次读取产生I / O开销吗?

2 个答案:

答案 0 :(得分:0)

我没有使用多个线程进行并发读取,而是使用this approach(基于一个巨大的CSV文件示例,其行必须通过HTTP同时发送):

  

同时在多个位置读取单个文件不会让你走得更快(但它可能会大大减慢你的速度)。

     

不是从多个线程读取文件,而是从单个线程读取文件,并并行化这些行的处理。单个线程应逐行读取CSV,并将每一行放入队列中。然后,多个工作线程应从队列中获取下一行,解析它,转换为请求,并根据需要同时处理请求。然后,通过单个线程完成工作的拆分,确保没有缺失的线或重叠。

如果您可以逐行阅读文件,LineIterator中的Commons IO可能会提高内存效率。如果你必须使用块,你的MappedByteBuffer似乎是一种合理的方法。对于队列,我使用具有固定容量的阻塞队列(例如ArrayBlockingQueue)来更好地控制内存使用(队列中的行/块+行中的行/块/内存中的行/块) )。

答案 1 :(得分:0)

FileChannel支持不同步的读取操作。它原生在Linux上使用pread

public abstract int read(ByteBuffer dst, long position) throws IOException

以下是FileChannel文档:

  

......其他行动,特别是那些采取明确立场的行动,可以同时进行;他们实际上是否这样做取决于基本的实施,因此没有具体说明。

通过返回读取的字节数是非常原始的(请参阅详细信息here)。但我认为你仍然可以利用它,假设“每个线程总是知道它需要读取的确切偏移量和字节长度”