使用Java多线程,协调查找最佳结果的最有效方法是什么?

让我说清楚,我在下面描述的方法是可操作的.我希望提高方法的吞吐量.它有效,而且效果很好.我们正在寻求进一步扩大吞吐量,这就是为什么我要研究这个问题.

手头的任务是提高评分算法的性能,该评分算法返回任务集合的最佳分数.我有使用ExecutorService执行评分的任务集合.每个任务检查它是否现在具有最佳分数,并且如果它是新的最佳分数,则以同步方式更新最佳分数.为了深入了解我正在工作的规模,每个任务只需要几分之一毫秒即可完成,但是有数千个任务完成,导致数百毫秒才能找到最好的任务.我每分钟执行这个评分算法几百次.结果是,60分钟中的30秒用于运行此评分算法.

当我的线程池是8个线程(具有24个虚拟核心)时,每个任务需要0.3毫秒.当我有20个线程(同一台机器,24个虚拟核心)时,每个任务需要0.6毫秒.我怀疑当我向我的ExecutorService线程池添加更多线程时,由于最佳分数上的同步(更多线程争用锁定),我的性能变差.

我做了很多搜索,但似乎找不到令人满意的(实际上,我似乎找不到任何)替代方案.我正在考虑收集所有分数,并按排序顺序存储,或者在完成所有任务后进行排序 – 但我不确定这是否会有任何改进.

有没有人对另一种更有效的收集最高分的方法有什么想法?

这是目前的方法:

final double[] bestScore = { Double.MAX_VALUE };
// for each item in the collection {
    tasks.add(Executors.callable(new Runnable() {
        public void run() {
            double score = //... do the scoring for the task
            if (score < bestScore[0]) {
                synchronized(bestScore) {
                    if (score < bestScore[0]) { // check again after we have the lock
                        bestScore[0] = score;
                        ...
                        // also save off other task identifiers in a similar fashion
                    }
                }
            }
        }
    }
} // end of loop creating scoring tasks

List<Future<Object>> futures = executorService.invokeAll(tasks /*...timeout params here*/);
... // handle cancelled tasks 

// now use the best scoring task that was saved off when it was found.
最佳答案
我必须理所当然地想要将每个单独的分数计算为提交给ExecutorService的单独任务.必须有其他好处,否则开销不值得.通常,您将实现一个Callable,在执行时返回分数(或具有分数和其他相关结果的对象).在成功调用所有任务之后,将在主线程中检查所有结果以获得最佳结果.

但是,考虑到您的约束,您可以尝试的一个优化是使用DoubleAccumulator,它适用于这类情况,而不是您的单元素数组和同步.它看起来像这样:

final DoubleAccumulator lowest = new DoubleAccumulator(Math::min, Double.POSITIVE_INFINITY);
/* Loop, creating all the tasks... */
for ( ... ) {
  tasks.add(Executors.callable(new Runnable() {
    public void run()
    {
      double score = 0; /* Compute a real score here. */
      lowest.accumulate(score);
    }
  }));
}
/* Invoke all the tasks, when successful... */
double lowestScore = lowest.get();

如果您需要跟踪分数以外的信息,您可以使用AtomicReference执行类似的操作,创建一个包含任务标识符,分数和任何其他所需属性的数据对象,并使用其中一个

如果你的任务是通过某种递归的,分而治之的方法初始化的,导致非阻塞,同等大小的任务,那么并行Stream底层的fork-join框架也可能是一个很好的选择.

但是,我再次指出,如果更多线程降低了性能,那么测量更少线程的使用似乎是谨慎的.

转载注明原文:使用Java多线程,协调查找最佳结果的最有效方法是什么? - 代码日志