具有大量任务的ExecutorService

时间:2018-06-28 13:39:31

标签: java multithreading threadpool executorservice

我有一个文件列表和一个分析这些文件的分析器列表。文件数量可以大(200,000),分析仪数量可以(1000)。因此,操作总数可能真的很大(200,000,000)。现在,我需要应用多线程来加快速度。我采用了这种方法:

ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (File file : listOfFiles) {
  for (Analyzer analyzer : listOfAnalyzers){
    executor.execute(() -> {
      boolean exists = file.exists();
      if(exists){
        analyzer.analyze(file);
      }
    });
  }
}
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

但是这种方法的问题是它占用了太多内存,我想有更好的方法来做到这一点。我仍然是Java和多线程的初学者。

2 个答案:

答案 0 :(得分:3)

2亿个任务将驻留在哪里?我希望,除非您打算以分布式方式实现解决方案,否则请不要将其存储在内存中。同时,您需要实例化一个不会{em>不累积大量队列的ExecutorService。创建here时,请与“主叫方运行策略”一起使用(请参阅service)。如果尝试在另一个任务已满时将其放入队列中,您将最终自己执行它,这可能就是您想要的。

OTOH,现在我更加认真地研究您的问题,为什么不同时分析单个文件?然后,队列永远不会大于分析器的数量。坦率地说,这就是我要做的,因为我想要一个可读的日志,该日志在加载文件时会以正确的顺序显示每个文件的消息。

很抱歉没有帮助:

analysts.stream().map(analyst -> executor.submit(() -> analyst.analyze(file))).map(Future::get);

基本上,为一个文件创建一堆期货,然后等待全部继续操作。

答案 1 :(得分:2)

一个想法是采用fork / join算法并将项目(文件)分组,以便分别处理它们。

我的建议如下:

  1. 首先,过滤掉所有不存在的文件-它们不必要地占用了资源。
  2. 以下伪代码演示了可以帮助您的算法:

    public static class CustomRecursiveTask extends RecursiveTask<Integer {
    
    private final Analyzer[] analyzers;
    
    private final int threshold;
    
    private final File[] files;
    
    private final int start;
    
    private final int end;
    
    public CustomRecursiveTask(Analyzer[] analyzers,
                               final int threshold,
                               File[] files,
                               int start,
                               int end) {
        this.analyzers = analyzers;
        this.threshold = threshold;
        this.files = files;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Integer compute() {
        final int filesProcessed = end - start;
        if (filesProcessed < threshold) {
            return processSequentially();
        } else {
            final int middle = (start + end) / 2;
            final int analyzersCount = analyzers.length;
    
            final ForkJoinTask<Integer> left =
                    new CustomRecursiveTask(analyzers, threshold, files, start, middle);
            final ForkJoinTask<Integer> right =
                    new CustomRecursiveTask(analyzers, threshold, files, middle + 1, end);
            left.fork();
            right.fork();
    
            return left.join() + right.join();
        }
    }
    
    private Integer processSequentially() {
        for (int i = start; i < end; i++) {
            File file = files[i];   
            for(Analyzer analyzer : analyzers) { analyzer.analyze(file) };
        }
    
        return 1;
    }
    }
    

用法如下:

 public static void main(String[] args) {
    final Analyzer[] analyzers = new Analyzer[]{};
    final File[] files = new File[] {};

    final int threshold = files.length / 5;

    ForkJoinPool.commonPool().execute(
            new CustomRecursiveTask(
                    analyzers,
                    threshold,
                    files,
                    0,
                    files.length
            )
    );
}

请注意,根据约束条件,您可以操纵任务的构造函数参数,以便算法可以调整到文件数量。

您可以根据文件数量指定不同的threshold

final int threshold;
if(files.length > 100_000) {
   threshold = files.length / 4;
} else {
   threshold = files.length / 8;
}

您还可以根据输入的数量在ForkJoinPool中指定辅助线程的数量。

测量,调整,修改,最终将解决问题。

希望有帮助。

更新:

如果结果分析没有意义,则可以将RecursiveTask替换为RecursiveAction。伪代码在这之间增加了自动装箱的开销。