同时求和的最佳方式

时间:2015-08-21 07:16:03

标签: java multithreading concurrency parallel-processing biginteger

我正在尝试计算一些大数字。为了加快计算速度,我想利用多线程。每个线程都应该计算一个数字,最后计算一个总和。

我曾经看过一些适用于SumThreadCollector的内容,如下所示:

public BigInteger compute(int p) {
    Collector c = new Collector(p);

    for(T element : Collection<T> bigCollection) {
        new SumThread(c) {

            @Override
            protected void doTheJob() {
                long big = someVeryComplexCalculation(element, ...); //n!
                receive(BigInteger.valueOf(big));
            }

        }
    }

    if(collector.isReady())
        return collector.getResult();

    return null;
}

public class Collector {

    private int numberOfProcesses;
    private int numberOfAllowedProcesses;
    private BigInteger result;

    public Collector(int n) {
        numberOfAllowedProcesses = n;
        numberOfProcesses = 0;
        result = BigInteger.ZERO;
    }

    synchronized public void enter() throws InterruptedException {
        if (numberOfProcesses == numberOfAllowedProcesses) wait();
        numberOfProcesses++;
    }

    synchronized public void leave() {
        numberOfProcesses--;
        notify();
    }

    synchronized public void register(BigInteger v) {
        result = result.add(v);
    }

    synchronized public boolean isReady() throws InterruptedException {
        while (numberOfProcesses > 0) wait();
        return true;
    }

    ...
}

public abstract class SumThread extends Thread {

    private Collector collector;

    public SumThread(Collector c) throws InterruptedException {
        collector = c;
        collector.enter();
    }

    abstract protected void doTheJob(); //complex calculations can be done in here

    public void receive(BigInteger t) {
        collector.register(t);
    }

    public void run() {
        doTheJob();
        collector.leave();
    }
}

我认为通过使用ExecutorService而不是像{{}}}一样,我可以轻松地超越这一点:

Thread

但是,此代码无法胜过public BigInteger compute(int p) { ExecutorService pool = Executors.newFixedThreadPool(p); Future<BigInteger>[] futures = new Future<BigInteger>[bigCollection.size()]; int i = 0; for(T element : Collection<T> bigCollection) { futures[i++] = p.submit(new Callable<BigInteger>() { @Override public BigInteger call() { long big = someVeryComplexCalculation(element, ...); //n! return BigInteger.valueOf(big); } } } // or with ExecutorCompletionService, but the loop remains I guess BigInteger res = BigInteger.ZERO for(Future<BigInteger> f : futures) res = res.add(f.get()); return res; } - SumThread解决方案。我也看到了关于Collector的事情,但我需要一些LongAdder的加法器......

我的问题是:同时计算金额的最佳方法是什么?它是上述之一还是有完全不同(但更好)的方式?

2 个答案:

答案 0 :(得分:7)

正如您所提到的LongAdder在Java-8中添加并使用有效最终变量,我假设您使用的是Java-8。在此版本中,解决任务的最佳方法是使用Stream API

BigInteger result = bigCollection.parallelStream()
                     .map(e -> BigInteger.valueOf(someVeryComplexCalculation(e, ...)))
                     .reduce(BigInteger.ZERO, BigInteger::add);

您的问题是经典的map-reduce任务,您应该在其中转换某些集合的每个元素,然后将各个转换的结果合并到最终结果中。 Stream API能够非常有效地并行化这些任务,而无需任何手动工作。在Oracle JDK中,任务在common ForkJoinPool pool中执行,默认情况下会创建与您拥有的CPU核心数一样多的线程。

答案 1 :(得分:2)

Tou有两个解决方案:

首先,我建议使用JDK7中的Fork-Join Framework来完成这项任务:

您需要实现RecursiveTask

作为第二个解决方案(JDK8)将使用pararell流,就像@ tagir-valeev所提议的那样。

在这两种情况下,它取决于您的用途以及您正在使用的Java版本。