我当时正在阅读Java ForkJoin框架。不直接调用invoke()
对ForkJoinTask
的实现(例如RecursiveTask
)有什么额外的好处,而是实例化ForkJoinPool
和调用pool.invoke(task)
?当我们调用这两个方法都称为invoke
时,究竟会发生什么?
从源代码来看,如果调用recursiveTask.invoke
,它将以托管线程池的方式调用其exec
和最终的compute
。因此,更让人困惑的是为什么我们有成语pool.invoke(task)
。
我编写了一些简单的代码来测试性能差异,但我没有看到任何代码。也许测试代码是错的?见下文:
public class MyForkJoinTask extends RecursiveAction {
private static int totalWorkInMillis = 20000;
protected static int sThreshold = 1000;
private int workInMillis;
public MyForkJoinTask(int work) {
this.workInMillis = work;
}
// Average pixels from source, write results into destination.
protected void computeDirectly() {
try {
ForkJoinTask<Object> objectForkJoinTask = new ForkJoinTask<>();
Thread.sleep(workInMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void compute() {
if (workInMillis < sThreshold) {
computeDirectly();
return;
}
int discountedWork = (int) (workInMillis * 0.9);
int split = discountedWork / 2;
invokeAll(new MyForkJoinTask(split),
new MyForkJoinTask(split));
}
public static void main(String[] args) throws Exception {
System.out.printf("Total work is %d in millis.%n", totalWorkInMillis);
System.out.printf("Threshold is %d in millis.%n", sThreshold);
int processors = Runtime.getRuntime().availableProcessors();
System.out.println(Integer.toString(processors) + " processor"
+ (processors != 1 ? "s are " : " is ")
+ "available");
MyForkJoinTask fb = new MyForkJoinTask(totalWorkInMillis);
ForkJoinPool pool = new ForkJoinPool();
long startTime = System.currentTimeMillis();
// These 2 seems no difference!
pool.invoke(fb);
// fb.compute();
long endTime = System.currentTimeMillis();
System.out.println("Took " + (endTime - startTime) +
" milliseconds.");
}
}
发布于 2015-12-07 23:39:04
compute()
类的RecursiveTask
方法只是一个包含任务代码的抽象方法。它不使用池中的新线程,如果您正常调用它,它不会在池托管线程中运行。
叉连接池上的invoke
方法向池提交一个任务,然后池开始在单独的线程上运行,调用该线程上的compute
方法,然后等待结果。
您可以在和ForkJoinPool中的措辞中看到这一点。invoke()
方法实际上执行任务,而compute()
方法只是封装计算。
受保护的抽象V计算() 这个任务所执行的主要计算。
和ForkJoinPool
公共T调用(ForkJoinTask任务) 执行给定任务,完成后返回其结果。..。
因此,使用计算方法,您要做的是在叉连接池之外运行对compute
的第一次调用。您可以通过在计算方法中添加日志行来测试这一点。
System.out.println(this.inForkJoinPool());
还可以通过记录线程id来检查它是否在同一个线程中运行。
System.out.println(Thread.currentThread().getId());
调用invokeAll
之后,该调用中包含的子任务将在池中运行。但是请注意,它不一定在调用compute()
之前创建的池中运行。您可以注释掉您的new ForkJoinPool()
代码,它仍将运行。有趣的是,java 7文档说,如果在池托管线程之外调用invokeAll()
方法,它将抛出异常,但是java 8文档没有,我还没有在java 7中测试它--请注意(只有8)。但是很有可能,在java 7中直接调用compute()
时,代码会抛出一个异常。
这两个结果同时返回的原因是毫秒不足以记录在池托管线程中启动第一个线程或仅在现有线程中运行第一个compute
调用的差异。
塞拉和贝茨的OCA/OCP学习指南推荐您使用叉连接框架的方式是从池中调用invoke()
。它清楚地说明了您使用的是哪个池,还意味着您可以向同一个池提交多个任务,这将节省每次重新创建新池的开销。从逻辑上讲,将所有的任务计算都保存在池托管线程(或者至少我认为是这样)中也是比较干净的。
pool.invoke()
调用特定池;而不是让框架来创建一个池,而不是在首次调用task.invoke
或task.invokeAll
时创建它。这意味着您可以将池重新用于新任务,并在创建池时指定活动线程的数量。这就是区别所在。将这些日志行添加到您的代码中,遍历它,您将看到它正在做什么
https://stackoverflow.com/questions/34132326
复制相似问题