我有一个要使用Java的ExecutorService
和Future
类解决的问题。我目前正在使用for循环从一个函数进行计算(每个样本可能要花费几分钟)的函数中获取大量样本(每个样本可能要花费几分钟)。我有一个FunctionEvaluator
类,可以为我评估该函数,并且由于包含大量内部内存,因此该类的实例化非常昂贵,因此我使该类易于使用一些内部计数器和reset()进行重用。方法。所以我目前的情况是这样的:
int numSamples = 100;
int amountOfData = 1000000;
double[] data = new double[amountOfData];//Data comes from somewhere...
double[] results = new double[numSamples];
//a lot of memory contained inside the FunctionEvaluator class,
//expensive to intialise
FunctionEvaluator fe = new FunctionEvaluator();
for(int i=0; i<numSamples; i++) {
results[i] = fe.sampleAt(i, data);//very expensive computation
}
但是我想获得一些多线程来加快处理速度。这应该足够容易,因为尽管每个样本将共享data
内部的任何内容,但这是一个只读操作,并且每个样本彼此独立。现在,因为我之前使用过Java的Future
和ExecutorService
,所以我对此不会有任何麻烦,但是从来没有在必须重复使用Callable
的情况下使用。因此,总的来说,如果我有能力运行n
的{{1}}实例化,我将如何设置这种情况?大概是这样的:
FunctionEvaluator
任何帮助将不胜感激!非常感谢。
编辑
因此,这是一个玩具示例(不起作用:P)。在这种情况下,我要采样的“昂贵函数”只是对整数进行平方,而对我有用的“昂贵实例化类”称为int numSamples = 100;
int amountOfData = 1000000;
int N = 10;
double[] data = new double[amountOfData];//Data comes from somewhere...
double[] results = new double[numSamples];
//a lot of memory contained inside the FunctionEvaluator class,
//expensive to intialise
FunctionEvaluator[] fe = new FunctionEvaluator[N];
for(int i=0; i<numSamples; i++) {
//Somehow add available FunctionEvaluators to an ExecutorService
//so that N FunctionEvaluators can run in parallel. When a
//FunctionEvaluator is finished, reset then compute a new sample
//until numSamples samples have been taken.
}
:
在TestConc.java中:
CallableComputation
在CallableComputation.java中:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class TestConc {
public static void main(String[] args) {
SquareCalculator squareCalculator = new SquareCalculator();
int numFunctionEvaluators = 2;
int numSamples = 10;
ExecutorService executor = Executors.newFixedThreadPool(2);
CallableComputation c1 = new CallableComputation(2);
CallableComputation c2 = new CallableComputation(3);
CallableComputation[] callables = new CallableComputation[numFunctionEvaluators];
Future<Integer>[] futures = (new Future[numFunctionEvaluators]);
int[] results = new int[numSamples];
for(int i=0; i<numFunctionEvaluators; i++) {
callables[i] = new CallableComputation(i);
futures[i] = executor.submit(callables[i]);
}
futures[0] = executor.submit(c1);
futures[1] = executor.submit(c2);
for(int i=numFunctionEvaluators; i<numSamples; ) {
for(int j=0; j<futures.length; j++) {
if(futures[j].isDone()) {
try {
results[i] = futures[j].get();
}
catch (InterruptedException e) {
e.printStackTrace();
}
catch (ExecutionException e) {
e.printStackTrace();
}
callables[j].set(i);
System.out.printf("Function evaluator %d given %d\n", j, i+1);
executor.submit(callables[j]);
i++;
}
}
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.MINUTES);
}
catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0; i<results.length; i++) {
System.out.printf("res%d=%d, ", i, results[i]);
}
System.out.println();
}
private static boolean areDone(Future<Integer>[] futures) {
for(int i=0; i<futures.length; i++) {
if(!futures[i].isDone()) {
return false;
}
}
return true;
}
private static void printFutures(Future<Integer>[] futures) {
for (int i=0; i<futures.length; i++) {
System.out.printf("f%d=%s | ", i, futures[i].isDone()?"done" : "not done");
}System.out.printf("\n");
}
}
答案 0 :(得分:2)
在Java8中:
double[] result = IntStream.range(0, numSamples)
.parallel()
.mapToDouble(i->fe.sampleAt(i, data))
.toArray();
问题询问如何通过加载尽可能多的CPU来并行执行繁重的计算功能。
并行计算涉及将问题分解为子问题, 同时解决这些问题(并行解决每个问题 子问题在单独的线程中运行),然后将 子问题解决方案的结果。 Java SE提供了 fork / join框架,使您可以更轻松地实现 应用程序中的并行计算。但是,有了这个框架, 您必须指定问题的细分方式。用 聚合操作后,Java运行时将执行此分区,并且 为您组合解决方案。
实际的解决方案包括:
IntStream.range将生成从0到numSamples的整数流。
parallel()将拆分流并执行该流,将使盒子上的所有可用CPU都运行。
mapToDouble()将通过应用将执行实际工作的lamba表达式将整数流转换为双精度流。
toArray()是一种终端操作,它将汇总结果并将其作为数组返回。
答案 1 :(得分:0)
不需要特殊代码更改,您可以一次又一次使用相同的Callable,而不会出现任何问题。同样,要提高效率,正如您所说的那样,创建FunctionEvaluator的实例非常昂贵,您只能使用一个实例并确保sampleAt是线程安全的。一种选择是,也许您可以使用所有函数局部变量,并且在任何线程运行时的任何时间都不要修改任何传递的参数
请在下面找到一个简单的示例:
代码段:
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<String> task1 = new Callable<String>(){public String call(){System.out.println(Thread.currentThread()+"currentThread");return null;}}
executor.submit(task1);
executor.submit(task1);
executor.shutdown();
请在下面找到屏幕截图:
答案 2 :(得分:0)
您可以将每个FunctionEvaluator的实际工作包装为Callable / Runnanle,然后使用带有队列的fixdThreadPool,然后只需将目标可调用/可运行对象汇总到threadPool。
答案 3 :(得分:0)
我想获得一些多线程来加快处理速度。
听起来不错,但是您的代码过于复杂。 @Pavel有一个简单的Java 8解决方案,但是即使没有Java 8,您也可以使它变得更加简单。
您需要做的就是将作业提交给执行程序,然后在返回的get()
的每一个上调用Future
。不需要Callable
类,尽管它确实使代码更简洁。但是您肯定不需要阵列,这是一种不好的模式,因为输入错误很容易产生越界异常。坚持收集或Java 8流。
ExecutorService executor = Executors.newFixedThreadPool(2);
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
for (int i = 0; i < numSamples; i++ ) {
// start the jobs running in the background
futureList.add(executor.subject(new CallableComputation(i));
}
// shutdown executor if done submitting tasks, submitted jobs will keep running
executor.shutdown();
for (Future<Integer> future : futureList) {
// this will wait for the future to finish, it also throws some exceptions
Integer result = future.get();
// add result to a collection or something here
}