我正在创建一个RandomAccessFile
对象,以便通过多个线程写入文件(在SSD上)。每个线程都尝试在文件中的特定位置写一个直接字节缓冲区,并确保线程写入的位置不会与另一个线程重叠:
file_.getChannel().write(buffer, position);
其中file_
是RandomAccessFile
的实例,buffer
是直接字节缓冲区。
对于RandomAccessFile对象,由于我没有使用fallocate来分配文件,并且文件的长度正在改变,这是否会利用底层媒体的并发性?
如果不是,在创建文件时没有调用fallocate就可以使用上面的函数吗?
答案 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;避免这种情况。但同样,这可能取决于操作系统/文件系统。