[英]Concurrency of RandomAccessFile in Java
我正在创建一个RandomAccessFile
对象,以便通过多个线程写入文件(在SSD上)。 每个线程都尝试在文件中的特定位置写一个直接字节缓冲区,并确保线程写入的位置不会与另一个线程重叠:
file_.getChannel().write(buffer, position);
其中file_
是RandomAccessFile
的实例, buffer
是直接字节缓冲区。
对于RandomAccessFile对象,由于我没有使用fallocate来分配文件,并且文件的长度在变化,这是否会利用底层媒体的并发性?
如果不是,在创建文件时如果没有调用fallocate,使用上述函数是否有任何意义?
我用以下代码进行了一些测试:
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:线程在执行期间混合
再次如文档所述 :
文件通道可供多个并发线程使用。 可以在Channel接口指定的任何时间调用close方法。 在任何给定时间,只有一个涉及通道位置或可以更改其文件大小的操作可能正在进行中; 在第一个操作仍在进行时尝试启动第二个此类操作将阻塞,直到第一个操作完成。 其他操作,特别是那些采取明确立场的操作,可以同时进行; 他们实际上是否这样做取决于基本的实施,因此没有具体说明。
所以我建议先尝试一下,看看你要部署代码的OS(可能是文件系统类型)是否支持并行执行FileChannel.write
调用
编辑 :正如所指出的,上述并不意味着线程可以并发写入文件,它实际上是相反的,因为write
调用根据WritableByteChannel的合同行为,它明确指定一次只能有一个线程写入到给定的文件:
如果一个线程在通道上启动写操作,则尝试启动另一个写操作的任何其他线程将阻塞,直到第一个操作完成
正如文档所述并且Adonis已经提到过这一点,写入一次只能由一个线程执行。 你不会通过concurreny获得性能提升,而且,如果这是一个实际问题,你应该只担心性能,因为同时写入磁盘可能会降低你的性能(SSD可能比HDD更少)。
在大多数情况下,底层媒体(SSD,HDD,网络)是单线程的 - 实际上,硬件级别上没有线程,线程只不过是抽象。
在您的情况下,媒体是SSD。 虽然SSD内部可以同时向多个模块写入数据(它们可能达到平均值,写入速度可能快,甚至优于读取),但内部映射数据结构是共享资源,因此争用,尤其是在频繁更新(如并发)时写道。 尽管如此,这种数据结构的更新速度非常快,因此除非出现问题,否则无需担心。
但除此之外,这些只是SSD的内部。 在外部,您通过串行 ATA接口进行通信,因此一次一个字节(实际上是帧信息结构中的数据包,FIS)。 最重要的是OS / Filesystem再次具有可能的竞争数据结构和/或应用他们自己的优化方法,例如后写缓存。
此外,正如您所知道的媒体是什么,您可以特别优化它,当一个线程写入大量数据时,SSD非常快。
因此,您可以创建一个大的内存缓冲区(可能考虑一个内存映射文件),并同时写入此缓冲区,而不是使用多个线程进行写入。 只要你确保每个线程访问它自己的缓冲区地址空间,内存本身就没有争用。 完成所有线程后,将此缓冲区写入SSD(如果使用内存映射文件则不需要)。
另请参阅关于SSD开发的这个很好的总结: 摘要 - 每个程序员应该了解固态驱动器
进行预分配(或更准确地说, file_.setLength()
,其实际映射到ftruncate
)的重点是文件的大小调整可能会使用额外的循环,你可能不会避免这种情况。 但同样,这可能取决于OS / Filesystem。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.