在单独的线程中处理文件写入的正确方法

时间:2013-07-17 18:20:32

标签: java multithreading thread-safety threadpool executorservice

我希望多次写入文件(100k +),并且写入将通过片状网络进行。所以要做到这一点,我正在考虑使用Java ExecutorService来帮助生成线程,但我不确定哪种设置组合能够正确地实现以下功能:

  1. 一次只允许1次写入(当然是顺序)
  2. 允许写入充足的时间进行每次写入(比如5秒),此时只需保释
  3. 如果写入速度慢,请让Executor收集队列中的写入并等待。
  4. 在线程队列为空之前,不要让整个程序退出。
  5. 通过编写者分隔线程。即,如果相同的编写器出现在此函数中,则将其放入自己的队列中。如果有一个不同的编写器指针,请给它自己的队列(不需要在同一队列中放置单独的编写器)。
  6. 我相信这可以通过执行程序功能以及主程序对象上的.wait().notify()命令的组合来完成。但是,我只是不确定如何精确地使用执行程序API来完成这项工作。

    这是我得到的:

    private void writeToFileInSeperateThread(final PrintWriter writer, final String text) {
      ExecutorService executor = Executors.newSingleThreadExecutor();
      try {
        executor.submit(new Thread(new Runnable() {
          public void run() {
            writer.println(text);
          }
        })).get(5L, TimeUnit.SECONDS);
      } catch (Exception e) {
        e.printStackTrace();
      }
      executor.shutdown();
    }
    

    在单个进程中,该方法将被调用100k +次,所以我不确定每次都应该创建一个新的ExcutorService实例,还是使用相同的实例? (在我尝试使用同一个版本时,我不断获得我认为与.newSingleThreadExecutor()指令相关的异常。

    希望保持Java 5兼容,但Java 6还可以。在Windows XP / 7上运行。

    更新: 这似乎已经在初始测试中完成了这个技巧:

      private class WriterStringPair {
        public final PrintWriter writer;
        public final String text;
    
        public WriterStringPair(PrintWriter writer, String text) {
          this.writer = writer;
          this.text = text;
        }
      }
    
      private void writeTextInSeperateThread(Writer writer, String text) {
        try {
          textQueue.offer(new WriterStringPair(writer, text), 300L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
          errOut.println(e);
          e.printStackTrace();
        }
      }
    
      final BlockingQueue<WriterStringPair> textQueue = new ArrayBlockingQueue<WriterStringPair>(500);
    
      private void setWritingThread() {
        new Thread((new Runnable() {
          public void run() {
            WriterStringPair q;
            while (!shutdown && !Thread.currentThread().isInterrupted()) {
              try {
                q = textQueue.poll(1L, TimeUnit.SECONDS);
                if (q != null) {
                  q.writer.write(q.text + "\n");
                  q.writer.flush();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          }
        })).start();
      }
    

2 个答案:

答案 0 :(得分:3)

如果不了解有关您通过“片状”网络撰写文件的更多详细信息以及具体方法,我们很难给出详细信息。但是这里有一些事情要考虑。

我会弄清楚有多少并发编写器在这里为您提供最佳性能 - 或者目标上最可靠的输出。然后你应该启动固定数量的这些编写器,每个编写器都来自共享的BlockingQueue(如果重要的话,每个编写器一个队列)。您应该快速超过您的IO或网络带宽,因此从5个左右的作者开始,然后根据需要上下移动。

public void run() {
   writer.println(text);
}

是的,你不想在每行工作方面做这类事情。最好将String text放入BlockingQueue<String>,然后让您的编写器Runnable类在该队列中ExecutorService出列,并且只在队列为空时停止或者设置了shutdown布尔值。

正如Peter所提到的,你需要注意用排队的文本字符串填充内存。如果输入文字很大,您应该将BlockingQueue的限制设置为几百左右。

  

我不确定每次都应该创建一个新的ExecutorService实例,还是使用相同的实例?

当然你应该有一个服务而一遍又一遍地创建一个服务。

  

我相信这可以通过执行器功能以及主程序对象上的.wait()和.notify()命令的组合来完成。

如果写得正确,则不需要使用wait和notify。我有一个volatile boolean shutdown = false所有作家都在观看。它们中的每一个都使用查看关闭从文本队列中出列。类似的东西:

while (!shutdown && !Thread.currentThread().isInterrupgted()) {
    String text = textQueue.poll(1, TimeUnit.SECONDS);
    if (text != null) {
        // write the text
    }
}

如果写入失败或者您可以重试它或其他任何必要的东西。

答案 1 :(得分:2)

一些问题

  • println不会告诉您是否存在IOException,因此如果您想要一些保护以防止错误,这将无济于事。
  • 为每一行启动ExecutorService非常慢,比提交任务慢得多。
  • 创建大量任务不仅会非常缓慢,而且会耗尽你所有的记忆。
  • 您提交Runnable,而不是线程到ExecutorService
  • shutdown不会停止某个线程,例如它在写入时阻塞。这可能导致许多线程在试图同时写入所有线程。

我建议将数据保存到本地系统(如JMS或数据库或文件(例如Java-Chronicle)),并在可用时将数据复制到NFS。

假设您无法修复NFS,因此它不会出现问题。