我提出了以下问题:出于性能原因,我需要跨多个线程拆分工作,但我不确定采取什么方法。
首先,我要提供的任务应该返回一个值并获取一个参数。此外,主要方法(执行主要工作,而不是static main()
)已经在单独的线程上运行,并定期调用。此外,此方法必须在某个时刻等待所有线程完成然后继续。
一种方法(对我来说最明显)是将每个作业安排在一个单独的线程上并将结果存储在类变量中:
public Object result1, result2;
public void mainMethod() throws InterruptedException {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
result1 = expensiveMethod("param1");
}
});
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
result2 = expensiveMethod("param2");
}
});
thread1.join();
thread.join();
//Do rest of work
}
private Object expensiveMethod(Object param){
// Do work and return result
}
这有点难看并且不理想,因为正如我所说,mainMethod被多次调用,我不希望在设置结果变量时有任何竞争条件。理想情况下,我想让它们成为局部变量,但我不能在run方法中访问它们,除非它们是最终的,然后我不能为它们赋值......
我做的另一种方法就是:
public void mainMethod() throws InterruptedException, ExecutionException {
String obj1, obj2;
final ExecutorService executorService = Executors.newFixedThreadPool(16);
final Future<String> res1 = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return expensiveMethod("param1");
}
});
final Future<String> res2 = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return expensiveMethod("param2");
}
});
obj1 = res1.get();
obj2 = res2.get();
}
private String expensiveMethod(String param) {
// Do work and return result
}
这会自动等待main方法的这两个计算,并允许我在本地存储结果。你觉得怎么样?还有其他方法吗?
答案 0 :(得分:12)
使用ExecutorService
的方法几乎是最现代,最安全的方法。建议您将Callable
提取到单独的类:
public class ExpensiveTask implements Callable<String> {
private final String param;
public ExpensiveTask(String param) {
this.param = param;
}
@Override
public String call() throws Exception {
return expensiveMethod(param);
}
}
这将使您的代码更清晰:
final ExecutorService executorService = Executors.newFixedThreadPool(16);
final Future<String> res1 = executorService.submit(new ExpensiveTask("param1"));
final Future<String> res2 = executorService.submit(new ExpensiveTask("param2"));
String obj1 = res1.get();
String obj2 = res2.get();
一些注意事项:
如果您只想同时处理两个任务,或者您想从多个客户端线程重用该池,则16个线程太多了?
记得关闭游泳池
使用轻量级ExecutorCompletionService
等待完成的第一个任务,不一定是第一个已提交的任务。
如果您需要完全不同的设计理念,请查看akka及其基于actor的并发模型。
答案 1 :(得分:2)
首先,您可能希望从ExecutorService
外部化mainMethod()
的创建。如果经常调用它,则可能会创建大量线程。
Future
方法更好,因为这正是Futures的用途。此外,它使阅读代码更容易。
更轻松的说明,尽管您可能必须将对象定义为final,但无论您的引用是否为final,都可以在对象上设置setter方法,可能允许您更改final对象的值。 (参考文献不是最终对象!)
答案 2 :(得分:2)
您想使用CompletionService并跟踪提交的任务 在你的循环中,当你完成所有任务时,你拿()并退出循环 缩放非常好,您可以在以后添加更多任务。
答案 3 :(得分:1)
我将添加一个在我眼中更优雅的提案,而不是为参数化Callable
创建一个全新的类。我的解决方案是一个返回Callable
实例的方法:
Callable<String> expensive(final String param) {
return new Callable<String>() { public String call() {
return expensiveMethod(param);
}};
}
这甚至使客户端代码更加可口:
final Future<String> f1 = executor.submit(expensive("param1"));
答案 4 :(得分:1)
略有不同的方法是:
创建LinkedBlockingQueue
将其传递给每项任务。任务可以是j.u.c.Executor上的Threads或Runnables。
每个任务将其结果添加到队列
主线程在循环中使用queue.take()读取结果
这样一旦计算结果就会处理结果。
答案 5 :(得分:1)
private static final ExecutorService threadpool = Executors.newFixedThreadPool(3);
ArrayList<Future<List<Data>>> futures = new ArrayList<Future<List<Data>>>();
for (ArrayList<Data> data : map.values()) {
final Future<List<Data>> future = threadpool.submit(new ValidationTaskExecutor(data));
futures.add(future);
}
List<List<Data>> res = new ArrayList<List<Data>>();
for (Future<List<Data>> future : futures) {
allValidRequest.addAll(future.get());
}