12

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

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

4

6 回答 6

15

你的方法ExecutorService几乎是最现代和最安全的方法。建议将您Callable的 s 提取到单独的类中:

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 的并发模型。

于 2012-10-11T18:13:17.583 回答
2

稍微不同的方法是:

  • 创建一个 LinkedBlockingQueue

  • 将其传递给每个任务。任务可以是线程,也可以是 jucExecutor 上的 Runnables。

  • 每个任务将其结果添加到队列中

  • 主线程在循环中使用 queue.take() 读取结果

这种方式结果在计算后立即处理。

于 2012-10-11T18:39:57.473 回答
2

您想使用CompletionService并跟踪提交的任务。
然后在你的循环中,当你完成所有任务时,你 take() 并退出循环。
规模非常好,您稍后会添加更多任务。

于 2012-10-12T17:28:38.007 回答
2

首先,您可能希望ExecutorService从您的创建外部化mainMethod()如果这被频繁调用,您可能会创建很多线程。

Future方法更好,因为这正是 Futures 的用途。此外,它使阅读代码更容易。

稍微轻松一点,尽管您可能必须将对象定义为 final,但无论您的引用是否为 final,您始终可以在对象上使用 setter 方法,这可能允许您更改 final 对象的值。(引用是最终对象不是!)

于 2012-10-11T18:17:57.833 回答
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"));
于 2012-10-11T18:35:36.537 回答
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());

    }
于 2016-02-01T05:00:34.113 回答