我有一个文件列表和一个分析这些文件的分析器列表。文件数量可以大(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和多线程的初学者。
答案 0 :(得分:3)
2亿个任务将驻留在哪里?我希望,除非您打算以分布式方式实现解决方案,否则请不要将其存储在内存中。同时,您需要实例化一个不会{em>不累积大量队列的ExecutorService
。创建here时,请与“主叫方运行策略”一起使用(请参阅service)。如果尝试在另一个任务已满时将其放入队列中,您将最终自己执行它,这可能就是您想要的。
OTOH,现在我更加认真地研究您的问题,为什么不同时分析单个文件?然后,队列永远不会大于分析器的数量。坦率地说,这就是我要做的,因为我想要一个可读的日志,该日志在加载文件时会以正确的顺序显示每个文件的消息。
很抱歉没有帮助:
analysts.stream().map(analyst -> executor.submit(() -> analyst.analyze(file))).map(Future::get);
基本上,为一个文件创建一堆期货,然后等待全部继续操作。
答案 1 :(得分:2)
一个想法是采用fork / join算法并将项目(文件)分组,以便分别处理它们。
我的建议如下:
以下伪代码演示了可以帮助您的算法:
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
。伪代码在这之间增加了自动装箱的开销。