Java中RandomAccessFile的并发性

时间:2017-07-30 04:07:07

标签: java multithreading randomaccessfile filechannel

我正在创建一个RandomAccessFile对象,以便通过多个线程写入文件(在SSD上)。每个线程都尝试在文件中的特定位置写一个直接字节缓冲区,并确保线程写入的位置不会与另一个线程重叠:

file_.getChannel().write(buffer, position);

其中file_RandomAccessFile的实例,buffer是直接字节缓冲区。

对于RandomAccessFile对象,由于我没有使用fallocate来分配文件,并且文件的长度正在改变,这是否会利用底层媒体的并发性?

如果不是,在创建文件时没有调用fallocate就可以使用上面的函数吗?

2 个答案:

答案 0 :(得分:8)

我使用以下代码进行了一些测试:

   public class App {
    public static CountDownLatch latch;

    public static void main(String[] args) throws InterruptedException, IOException {
        File f = new File("test.txt");
        RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
        latch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(new WritingThread(i, (long) i * 10, file.getChannel()));
            t.start();

        }
        latch.await();
        file.close();
        InputStream fileR = new FileInputStream("test.txt");
        byte[] bytes = IOUtils.toByteArray(fileR);
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);

        }  
    }

    public static class WritingThread implements Runnable {
        private long startPosition = 0;
        private FileChannel channel;
        private int id;

        public WritingThread(int id, long startPosition, FileChannel channel) {
            super();
            this.startPosition = startPosition;
            this.channel = channel;
            this.id = id;

        }

        private ByteBuffer generateStaticBytes() {
            ByteBuffer buf = ByteBuffer.allocate(10);
            byte[] b = new byte[10];
            for (int i = 0; i < 10; i++) {
                b[i] = (byte) (this.id * 10 + i);

            }
            buf.put(b);
            buf.flip();
            return buf;

        }

        @Override
        public void run() {
            Random r = new Random();
            while (r.nextInt(100) != 50) {
                try {
                    System.out.println("Thread  " + id + " is Writing");
                    this.channel.write(this.generateStaticBytes(), this.startPosition);
                    this.startPosition += 10;
                } catch (IOException e) {
                    e.printStackTrace();

                }
            }
            latch.countDown();
        }
    }
}

到目前为止我所看到的:

  • Windows 7(NTFS分区):线性运行(也就是一个线程写入,当它结束时,另一个线程可以运行)

  • Linux Parrot 4.8.15(ext4分区)(基于Debian的发行版),Linux内核4.8.0:线程在执行期间混合

再次documentation说:

  

多个并发线程可以安全地使用文件通道。该   可以在Channel指定的任何时间调用close方法   接口。只有一个操作涉及渠道的位置或   可以在任何给定时间更改其文件的大小;   尝试在第一次操作时启动第二次此类操作   正在进行将阻止,直到第一个操作完成。其他   操作,特别是那些采取明确立场的操作,可以   同时进行; 他们实际上是否这样做取决于   基础实施,因此未指明。

所以我建议先尝试一下,看看你要部署代码的OS(可能是文件系统类型)是否支持并行执行FileChannel.write调用

编辑:正如所指出的,上述并不意味着线程可以同时写入文件,它实际上与write调用根据合同的行为相反。一个WritableByteChannel明确指出一次只有一个线程可以写入给定文件:

  

如果一个线程在某个通道上启动写操作,那么任何一个   尝试启动另一个写操作的其他线程将   阻止,直到第一个操作完成

答案 1 :(得分:7)

正如文档所述并且Adonis已经提到过这一点,一次只能由一个线程执行写操作。您不会通过concurreny获得性能提升,而且,如果它是一个实际问题,您应该只担心性能,因为同时写入磁盘可能会降低您的性能(SSD可能比HDD更少)。

底层媒体在大多数情况下(SSD,HDD,网络)是单线程的 - 实际上,硬件级别上没有线程,线程只不过是抽象。

在您的情况下,媒体是SSD。 虽然SSD内部可以同时向多个模块写入数据(它们可能达到平均值,写入速度可能快,甚至优于读取),但内部映射数据结构是共享资源,因此争用,尤其是在频繁更新(如并发)时写道。尽管如此,这种数据结构的更新速度非常快,因此除非它成为一个问题,否则无需担心。

但除此之外,这些只是SSD的内部。在外部,您通过串行 ATA接口进行通信,因此一次一个字节(实际上是帧信息结构中的数据包,FIS)。最重要的是OS / Filesystem再次具有可能的竞争数据结构和/或应用他们自己的优化方法,例如后写缓存。

此外,正如您所知道的媒体是什么,您可以特别优化它,当一个线程写入大量数据时,SSD非常快。

因此,您可以创建一个大型的内存缓冲区(可能考虑一个内存映射文件)并同时写入此缓冲区,而不是使用多个线程进行写入。只要您确保每个线程访问它自己的缓冲区地址空间,内存本身就不会争用。完成所有线程后,将这一个缓冲区写入SSD(如果使用内存映射文件则不需要)。

另见关于SSD开发的这个很好的总结: A Summary – What every programmer should know about solid-state drives

进行预分配(或者更确切地说,print,其实际映射到file_.setLength())的重点是文件的大小调整可能会使用额外的周期而你可能会&# 39;避免这种情况。但同样,这可能取决于操作系统/文件系统。