我正在尝试计算一些大数字。为了加快计算速度,我想利用多线程。每个线程都应该计算一个数字,最后计算一个总和。
我曾经看过一些适用于SumThread
和Collector
的内容,如下所示:
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
的加法器......
我的问题是:同时计算金额的最佳方法是什么?它是上述之一还是有完全不同(但更好)的方式?
答案 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版本。