【问题标题】:Writing a file using multiple threads使用多个线程写入文件
【发布时间】:2015-11-26 19:53:49
【问题描述】:
我正在尝试使用多个线程在 Java 中编写一个大文件。
我已经尝试过 Java 中的 FileWriter 和 bufferedWriter 类。
正在写入的内容实际上是使用CopyManager 读取和写入的整个表(Postgres)。文件中的每一行都是表中的一个元组,我一次要写 100 行。
写法:
单个待写文件被多个线程以追加方式打开。
此后每个线程都尝试写入文件file。
以下是我面临的问题:
- 文件的内容有时会被覆盖,即:一行仍然不完整,下一行从那里开始。我的假设是 writer 的缓冲区已满。这迫使写入者立即将数据写入文件。写入的数据可能不是完整的一行,在它可以写入其余部分之前,下一个线程将其内容写入文件。
- 在使用
Filewriter 时,我偶尔会在文件中看到一条黑线。
有什么建议,如何避免这个数据完整性问题?
【问题讨论】:
标签:
java
multithreading
postgresql
file-io
bufferedwriter
【解决方案1】:
与其拥有同步方法,更好的解决方案是拥有一个由阻塞队列支持的单线程线程池。将要写入的消息应用程序将被推送到阻塞队列。日志写入线程将继续从阻塞队列中读取(如果队列为空,将被阻塞)并将继续将其写入单个文件。
【解决方案2】:
共享资源 == 争用
根据定义写入普通文件是一个序列化操作。尝试从多个线程写入它不会获得任何性能,I/O 是一种有限的有界资源,其带宽甚至比最慢或最过载的 CPU 还要少几个数量级。
对共享资源的并发访问可能很复杂(而且速度很慢)
如果您有多个线程正在执行昂贵的计算,那么您可以选择,如果您只是使用多个线程,因为您认为您会加快某些事情的速度,那么您只会做相反的事情。 I/O 争用总是会减慢对资源的访问速度,但由于锁等待和其他开销,它永远不会加快速度。
您必须有一个受保护的关键部分,并且一次只允许一个写入者。只需查找任何支持并发的日志编写器的源代码,您就会发现只有一个线程写入文件。
如果您的应用主要是:
-
CPU Bound:您可以使用一些锁定机制/数据结构来一次只让多个线程中的一个线程写入文件,从并发的角度来看,这作为一种幼稚的解决方案是没有用的;如果这些线程受 CPU 限制且 I/O 很少,这可能会起作用。
-
I/O Bound: 这是最常见的情况,您必须使用带有某种队列的消息传递系统,并让所有线程发布到队列/缓冲区,并且只有一个线程从中拉出并写入文件。这将是最具可扩展性和最容易实施的解决方案。
日志 - 异步写入
如果您需要创建单个超大文件,其中写入顺序不重要且程序受 CPU 限制,您可以使用日志技术。
让每个process 写入一个单独的文件,然后在最后将多个文件连接成一个大文件。这是一个非常老派的低技术解决方案,效果很好,并且已经使用了几十年。
显然,您拥有的存储 I/O 越多,这将在最后连接时执行得越好。
【解决方案3】:
我正在尝试使用多个线程在 Java 中编写一个大文件。
我建议您使用 X 个线程从数据库中读取数据,并让一个线程写入您的输出文件。与执行文件锁定等相比,这将更容易实现。
您可以使用共享的BlockingQueue(可能是ArrayBlockingQueue),这样数据库读取器将add(...) 加入队列,而您的写入器将在队列中的take() 循环中。当阅读器完成后,他们可以添加一些特殊的IM_DONE 字符串常量,一旦写入线程看到这些常量中的 X 个(即每个阅读器一个),它就会关闭输出文件并退出。
那么你可以使用单个BufferedWriter 而不需要任何锁等。您可能会被数据库调用而不是本地 IO 阻塞。当然,额外的线程根本不会减慢您的速度。
单个待写文件被多个线程以追加方式打开。此后每个线程都尝试写入文件file。
如果您坚持让您的阅读线程也进行写入,那么您应该在对单个共享BufferedWriter 的访问周围添加一个synchronized 块——您可以在BufferedWriter 对象本身上进行同步。知道何时关闭 writer 有点问题,因为每个线程都必须知道另一个线程是否已经退出。每个线程可以在运行时增加共享的AtomicInteger,并在完成时减少。然后查看运行计数并看到 0 的线程将关闭编写器。