【问题标题】:Threads and file writing线程和文件写入
【发布时间】:2016-02-01 20:52:34
【问题描述】:

我有一个使用 20 个线程的 java 程序。他们每个人都将他们的结果写入一个名为output.txt 的文件中。

output.txt 中的行数总是不同。

会不会是线程同步的问题?有办法处理吗?

【问题讨论】:

  • 嗯,这不是很清楚你的实现如何。正如我的简单测试用例所示,我使用具有 20 个线程的 FileWriter 获得恒定的输出行。可能需要添加一些实现细节。看我的回答。

标签: java multithreading


【解决方案1】:

会不会是线程同步的问题?

是的。

有办法解决这个问题吗?

是的,通过在相关互斥体上同步来确保写入序列化。或者,只有 一个 线程实际输出到文件,并让所有其他线程简单地将要写入的文本排队到一个写入线程从中提取的队列中。 (这样 20 个主线程就不会阻塞 I/O。)

关于互斥锁:例如,如果他们都使用相同的FileWriter 实例(或其他),我将其称为fw,然后他们可以使用它作为互斥体:

synchronized (fw) {
    fw.write(...);
}

如果他们每个人都使用自己的 FileWriter 或其他任何东西,请找到他们共享的其他东西作为互斥体。

但同样,让一个线程代表其他线程执行 I/O 可能也是一个好方法。

【讨论】:

  • 您能否更具体地说明一下单作者线程方法的最佳方法是什么?例如。也许使用single thread Executor 并将任务提交给Executor
【解决方案2】:

我建议您以这种方式组织它:一个线程使用者将消耗所有数据并将其写入文件。所有工作线程都会以同步的方式向消费者线程产生数据。或者在编写多线程文件时,您可以使用一些互斥锁或锁实现。

【讨论】:

  • +1 我只是在您编写答案时将此建议添加到我的答案中。 :-)
【解决方案3】:

在这种情况下,您应该使用同步。想象一下,2 个线程(t1 和 t2)同时打开文件并开始写入。第一个线程执行的更改被第二个线程覆盖,因为第二个线程是最后一个将更改保存到文件的线程。当线程 t1 正在写入文件时,t2 必须等到 t1 完成它的任务才能打开它。

【讨论】:

    【解决方案4】:

    如果您想要任何表面上的性能和易于管理,请按照 Alex 和其他人的建议使用生产者-消费者队列和一个文件编写器。让文件中的所有线程都使用互斥锁只是混乱 - 每个磁盘延迟都直接传输到您的主应用程序功能中(增加了争用)。这对于速度较慢的网络驱动器尤其无趣,而且往往会在没有警告的情况下消失。

    【讨论】:

      【解决方案5】:

      如果您可以将文件保存为FileOutputStream,您可以像这样锁定它:

      FileOutputStream file = ...
      ....
      // Thread safe version.
      void write(byte[] bytes) {
        try {
          boolean written = false;
          do {
            try {
              // Lock it!
              FileLock lock = file.getChannel().lock();
              try {
                // Write the bytes.
                file.write(bytes);
                written = true;
              } finally {
                // Release the lock.
                lock.release();
              }
            } catch ( OverlappingFileLockException ofle ) {
              try {
                // Wait a bit
                Thread.sleep(0);
              } catch (InterruptedException ex) {
                throw new InterruptedIOException ("Interrupted waiting for a file lock.");
              }
            }
          } while (!written);
        } catch (IOException ex) {
          log.warn("Failed to lock " + fileName, ex);
        }
      }
      

      【讨论】:

      • 鉴于synchronized 存在,这完全是多余的。
      • @EJP - 见FileLock - 文件上的锁应该对所有有权访问该文件的程序可见,无论这些程序是用什么语言编写的 i> - 所以,理论上,它们应该synchronized更好,但通常不是。
      • 这并不意味着它是非冗余的,或者“比synchronized更好”。问题中没有关于其他进程或其他语言的任何内容。
      【解决方案6】:

      好吧,没有任何实现细节,很难知道,但正如我的测试用例所示,我总是得到 220 行的输出,即恒定的行数,FileWriter。注意这里没有使用synchronized

      import java.io.File;
      import java.io.FileWriter;
      import java.io.IOException;
      /**
       * Working example of synchonous, competitive writing to the same file.
       * @author WesternGun
       *
       */
      public class ThreadCompete implements Runnable {
          private FileWriter writer;
          private int status;
          private int counter;
          private boolean stop;
          private String name;
      
      
          public ThreadCompete(String name) {
              this.name = name;
              status = 0;
              stop = false;
              // just open the file without appending, to clear content
              try {
                  writer = new FileWriter(new File("test.txt"), true);
              } catch (IOException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
      
          }
      
      
          public static void main(String[] args) {
      
              for (int i=0; i<20; i++) {
                  new Thread(new ThreadCompete("Thread" + i)).start();
              }
          }
      
          private int generateRandom(int range) {
              return (int) (Math.random() * range);
          }
      
          @Override
          public void run() {
              while (!stop) {
                  try {
                      writer = new FileWriter(new File("test.txt"), true);
                      if (status == 0) {
                          writer.write(this.name + ": Begin: " + counter);
                          writer.write(System.lineSeparator());
                          status ++;
                      } else if (status == 1) {
                          writer.write(this.name + ": Now we have " + counter + " books!");
                          writer.write(System.lineSeparator());
                          counter++;
                          if (counter > 8) {
                              status = 2;
                          }
      
                      } else if (status == 2) {
                          writer.write(this.name + ": End. " + counter);
                          writer.write(System.lineSeparator());
                          stop = true;
                      }
                      writer.flush();
                      writer.close();
                  } catch (IOException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
              }
          }
      }
      

      据我了解(和测试),此过程分为两个阶段:

      • 池中的所有线程都已创建并启动,准备抓取文件;
      • 其中一个抓住了它,我猜它然后在内部锁定了它,阻止其他线程访问,因为我从来没有看到来自两个线程的内容组合的行。因此,当一个线程正在写入时,其他线程正在等待,直到它完成该行,并且很可能会释放文件。 因此,不会发生竞争条件。
      • 其他人中最快的人拿起文件并开始写入。

      嗯,就像一群人在浴室外等着,不用排队.....

      因此,如果您的实现不同,请显示代码,我们可以帮助分解它。

      【讨论】:

      • 您可以尝试在两个 writer.write() 行之间添加 sleep。这样你可能会看到它不是线程安全的
      • 这不是一个真正的多线程示例,因为 for 循环将串行调用线程。要真正模拟多线程,可以添加一个计时器,以随机间隔重复触发函数调用以查看冲突。
      猜你喜欢
      • 2011-12-31
      • 2023-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-14
      相关资源
      最近更新 更多