Java - 将工作分割为多个线程

时间:2012-10-11 18:07:51

标签: java multithreading executorservice

我提出了以下问题:出于性能原因,我需要跨多个线程拆分工作,但我不确定采取什么方法。

首先,我要提供的任务应该返回一个值并获取一个参数。此外,主要方法(执行主要工作,而不是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方法的这两个计算,并允许我在本地存储结果。你觉得怎么样?还有其他方法吗?

6 个答案:

答案 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等待完成的第一个任务,不一定是第一个已提交的任务。

如果您需要完全不同的设计理念,请查看及其基于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());

    }