使用多个线程编写文件

我试图使用多个线程在Java中编写一个巨大的文件。

我在Java中尝试过FileWriterbufferedWriter类。

正在编写的内容实际上是使用CopyManager读取并写入的整个表(Postgres)。 文件中的每一行都是表格中的一个元组,我一次写入100行。

写作方法:

在附加模式下,多个线程打开单个待写文件。 之后每个线程都尝试写入文件文件。

以下是我面临的问题:

  • 有一段时间,文件的内容会被覆盖,即:一行保持不完整,下一行从那里开始。 我的假设是写入器的缓冲区已满。 这会强制编写器立即将数据写入文件。 写入的数据可能不是一个完整的行,在它可以写入余数之前,下一个线程将其内容写入文件。
  • 使用Filewriter ,我会在文件中看到一条黑线。

有什么建议,如何避免这种数据完整性问题?

共享资源==争用

根据定义写入普通文件是序列化操作。 尝试从多个线程写入它不会获得任何性能,I / O是一个有限的有限资源,其带宽比最慢或最负载最多的CPU低几个数量级。

对共享资源的并发访问可能很复杂(而且速度慢)

如果你有多个线程进行昂贵的计算,那么你有选择,如果你只是使用多个线程,因为你认为你要加快速度,你只是反过来。 争用I / O总是会减慢对资源的访问速度,因为锁等待和其他开销,它永远不会加快速度。

您必须拥有一个受保护的关键部分,并且一次只允许一个编写者。 只需查看支持并发的任何日志记录编写器的源代码,您将看到只有一个线程写入该文件。

如果您的申请主要是:

  1. CPU绑定:你可以使用一些锁定机制/数据结构,只让一个线程中的一个线程一次写入文件,从并发的角度来看,这是无用的解决方案; 如果这些线程是CPU绑定的I / O很少,这可能会起作用。

  2. I / O界限:这是最常见的情况,您必须使用具有某种队列的消息传递系统,并将所有线程发布到队列/缓冲区并从中拉出一个线程并写入该文件。 这将是最具扩展性和最易于实施的解决方案。

日记 – 异步写入

如果需要创建单个超大文件,其中写入顺序不重要且程序受CPU限制,则可以使用日记技术。

让每个process写入一个单独的文件,然后在最后将多个文件连接成一个大文件。 这是一个非常老派的低技术解决方案,运行良好,已有数十年。

显然,你拥有的存储I / O越多,这将在最终concat上执行得越好。

我试图使用多个线程在Java中编写一个巨大的文件。

我建议您从数据库中读取X个线程,并将一个线程写入输出文件。 与执行文件锁定等相比,这将更容易实现。

您可以使用共享的BlockingQueue (可能是ArrayBlockingQueue ),因此数据库读取器会将add(...)到队列中,而您的ArrayBlockingQueue器将位于队列的take()循环中。 当读者完成时,他们可以添加一些特殊的IM_DONE字符串常量,并且一旦写入线程看到这些常量的X(即每个读者一个),它就会关闭输出文件并退出。

那么你可以使用一个没有任何锁等的BufferedWriter 。 您可能会被数据库调用而不是本地IO阻止。 当然,额外的线程根本不会让你失望。

在附加模式下,多个线程打开单个待写文件。 之后每个线程都尝试写入文件文件。

如果您坚持让您的读取线程也进行写入,那么您应该围绕访问单个共享BufferedWriter添加一个synchronized块 – 您可以在BufferedWriter对象本身上进行同步。 知道何时关闭编写器是一个问题,因为每个线程都必须知道另一个线程是否已退出。 每个线程在运行时可以递增共享的AtomicInteger ,在它们完成时递减。 然后,查看run-count并看到0的线程将是关闭writer的线程。

而不是使用同步方法,更好的解决方案是让一个线程池具有由阻塞队列支持的单线程。 消息应用程序将写入将被推送到阻塞队列。 日志写入器线程将继续从阻塞队列中读取(在队列为空时将被阻止)并继续将其写入单个文件。