使用并发类并行处理目录中的文件

时间:2012-07-27 18:03:38

标签: java multithreading java.util.concurrent

我试图弄清楚如何使用java.util.concurrent包中的类型来并行处理目录中的所有文件。

我熟悉Python中的多处理软件包,它非常易于使用,所以理想情况下我正在寻找类似的东西:

public interface FictionalFunctor<T>{
  void handle(T arg);
}

public class FictionalThreadPool {
  public FictionalThreadPool(int threadCount){
    ...
  }
  public <T> FictionalThreadPoolMapResult<T> map(FictionalFunctor<T> functor, List<T> args){
    // Executes the given functor on each and every arg from args in parallel. Returns, when
    // all the parallel branches return.
    // FictionalThreadPoolMapResult allows to abort the whole mapping process, at the least.
  }
}

dir = getDirectoryToProcess();
pool = new FictionalThreadPool(10);   // 10 threads in the pool
pool.map(new FictionalFunctor<File>(){ 
  @Override
  public void handle(File file){
    // process the file
  }
}, dir.listFiles());

我觉得java.util.concurrent中的类型允许我做类似的事情,但我完全不知道从哪里开始。

有什么想法吗?

感谢。

编辑1

根据答案中给出的建议,我写了类似的东西:

public void processAllFiles() throws IOException {
  ExecutorService exec = Executors.newFixedThreadPool(6);
  BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<Runnable>(5); // Figured we can keep the contents of 6 files simultaneously.
  exec.submit(new MyCoordinator(exec, tasks));
  for (File file : dir.listFiles(getMyFilter()) {
    try {
      tasks.add(new MyTask(file));
    } catch (IOException exc) {
      System.err.println(String.format("Failed to read %s - %s", file.getName(), exc.getMessage()));
    }
  }
}

public class MyTask implements Runnable {
  private final byte[] m_buffer;
  private final String m_name;

  public MyTask(File file) throws IOException {
    m_name = file.getName();
    m_buffer = Files.toByteArray(file);
  }

  @Override
  public void run() {
    // Process the file contents
  }
}

private class MyCoordinator implements Runnable {
  private final ExecutorService m_exec;
  private final BlockingQueue<Runnable> m_tasks;

  public MyCoordinator(ExecutorService exec, BlockingQueue<Runnable> tasks) {
    m_exec = exec;
    m_tasks = tasks;
  }

  @Override
  public void run() {
    while (true) {
      Runnable task = m_tasks.remove();
      m_exec.submit(task);
    }
  }
}

想法代码的工作原理是:

  1. 一个接一个地读取文件。
  2. 文件内容保存在专用的MyTask实例中。
  3. 容量为5的阻塞队列来保存任务。我依靠服务器一次保留最多6个文件的内容的能力 - 队列中的5个和等待进入队列的另一个完全初始化的任务。
  4. 一个特殊的MyCoordinator任务从队列中提取文件任务,并将它们分派到同一个池中。
  5. 好的,所以有一个错误 - 可以创建超过6个任务。即使所有池线程都忙,也会提交一些。我打算稍后再解决。

    问题是它根本不起作用。第一次移除MyCoordinator线程阻塞 - 这很好。但它永远不会解锁,即使新任务被放入队列中。谁能告诉我我做错了什么?

3 个答案:

答案 0 :(得分:1)

您正在寻找的线程池是ExecutorService类。您可以使用newFixedThreadPool创建固定大小的线程池。这允许您轻松实现生产者 - 消费者模式,池为您封装所有队列和工作人员功能:

ExecutorService exec = Executors.newFixedThreadPool(10);

然后,您可以以类型实现Runnable的对象的形式提交任务(如果您还想获得结果,则可以Callable):

class ThreadTask implements Runnable {
    public void run() {
       // task code
    }
}

...

exec.submit(new ThreadTask());
// alternatively, using an anonymous type
exec.submit(new Runnable() {
           public void run() {
              // task code
           }
      });

关于并行处理多个文件的一个重要建议:如果您有一个机械磁盘保存文件,那么使用单个线程逐个读取它们是明智之举并提交每个文件文件到上面的线程池任务,进行处理。不要并行执行实际读数,因为它会降低性能。

答案 1 :(得分:1)

比使用ExecuterService更简单的解决方案是实现您自己的生产者 - 消费者方案。创建一个创建任务并提交到LinkedBlockingQueue或ArrayBlockingQueue的线程,并让工作线程检查此队列以检索任务并执行它们。您可能需要一种特殊的任务名称ExitTask来强制工人退出。因此,在作业结束时,如果您有n个工作人员,则需要将n个ExitTasks添加到队列中。

答案 2 :(得分:1)

基本上,@ Tudor说,使用ExecutorService,但我想扩展他的代码,我总是觉得很奇怪编辑其他人的帖子。以下是您将提交给ExecutorService的内容的简要说明:

public class MyFileTask implements Runnable {
   final File fileToProcess;

   public MyFileTask(File file) {
      fileToProcess = file;
   }

   public void run() {
      // your code goes here, e.g.
      handle(fileToProcess);
      // if you prefer, implement Callable instead
   }
}

另见my blog post here for some more details if you get stuck

由于处理文件经常导致IOExceptions,我更喜欢Callable(可以抛出一个已检查的Exception)到Runnable,但是YMMV。

相关问题