Java foreach循环中的并发-非阻塞设计

Java foreach循环中的并发-非阻塞设计,java,multithreading,concurrency,future,Java,Multithreading,Concurrency,Future,我正在处理一个返回列表对象的服务 有两种对象可以构建响应。其中一个从内部数据填充内容,另一个接触服务并获取数据 for(Item item : items){ if(item is external){ call service and get result and populate Response and add to List } if(item is internal) { populate response object and add t

我正在处理一个返回
列表
对象的服务

有两种对象可以构建响应。其中一个从内部数据填充内容,另一个接触服务并获取数据

for(Item item : items){
   if(item is external){
     call service and get result and populate Response and add to List
   }

   if(item is internal)
   {
     populate response object and add to list
   }
}
当前的impl是过程性的和阻塞性的,我想要的是一个非阻塞性的设计,这样,如果项目是外部的,就触发调用并继续执行列表中的下一个项目。然后,当循环完成时,我可以等待所有操作完成

这样做的好方法是什么?我还考虑为每个职责创建单独的类


编辑:引入的原因是为了减少延迟点击

我要做的是将所有操作包装在
Callable
s中,提交给
ExecutorService
,将返回的
Future
存储在列表中


然后,当它们全部完成时,在主线程中填充结果列表。

要在项目为外部时保持运行,操作
调用服务
应启动某种异步活动(例如
线程
可运行
提交给某种
执行者
)。此活动应具有回调机制,以便在
获取结果
完成后,它可以执行任务的最后一部分:
填充响应并添加到列表

您必须同步访问
列表
或将所有访问安排为单线程。

(具有可记住已提交任务的回调的线程池)看起来像个傻瓜:

CompletionService<Response> executorService = 
    new ExecutorCompletionService<>(Executors.newFixedThreadPool(10));
int totalExternal = 0;

for(Item item : items){
    if(item is external){
        executorService.submit(externalCall(item));
        ++totalExternal;
    }

    if(item is internal){
        populate response object and add to list
    }
}

for (int i = 0; i < totalExternal; ++totalExternal) {
    addAsynchResultToResponseList(executorService.take().get());
}
很明显,一旦异步,结果列表可以有任意顺序


另一种方法是使用普通的
执行器服务
,并有一个中间的
列表
。诀窍是使用包装器包装内部响应(它创建
Future
,立即完成并返回传递的值)

List futures=new ArrayList();
用于(项目:项目){
如果(项目是外部的){
futures.add(executorService.submit(externalCall(item)));
}
如果(项目是内部的){
添加(新的AsyncResult(synchResponse));
}
}
现在,您可以简单地迭代
期货
AsyncResult
将立即返回,因为该值在创建时已在计算机中(
synchResponse
)。但是您必须等待从线程池返回的
Future
s


请记住,
Future.get()
允许您检索原始异常。此外,列表中的
Future
s的顺序与原始项目的顺序相同,因此如果第n个
Future
失败,则
items
列表中的第n个项目是原因。

所有操作都不需要可调用,只有外部实体需要。如果该实体是内部的,我将数据存储在内存中,我不需要增加线程开销。@DarthVader我怀疑这会显著影响性能。我的原则是总是先尝试简单的解决方案,然后再优化。我同意@biziclop所说的。毕竟,(答案比问题更相关)。优化您的代码如果导致问题,请先编写更干净的解决方案,这样在几个月或几年后您或其他人再次查看代码时才有意义。
CompletionService
运行良好,因此您的解决方案是正确的,但是,当您关心按结果完成的顺序获取结果时,使用
CompletionService
更合适(因此它被称为
CompletionService
)。维护从执行器返回的
未来
对象的列表,然后对它们进行迭代以获得结果就足够了。此外,他不再有任意的顺序,而是按照提交顺序。我对顺序不感兴趣,只要它们返回,但是,如果失败,我必须处理异常或超时。还需要知道哪一个失败了。@Brian:嗯,你说得对,+1。我选择了
CompletionService
,以避免额外的列表,并将所有内容封装起来。另一方面,需要额外的
totalExternal
变量。我不确定while(executionService.poll()!=null)在这里是否安全(?)如果您想要顺序,只需将任务的索引传递给可调用对象,并将其与原始返回值一起返回。@TomaszNurkiewicz IIRC,这将不安全,因为
poll
在没有准备好的情况下返回
null
,即使仍有任务在执行。您可能需要考虑的另一件事是:当两个线程试图同时访问同一个外部项时,会发生什么情况?除非您提供某种资源锁定,否则它可能会导致两个外部调用,当第二个调用可以等待第一个调用完成时,会导致不必要的工作+1,问得好。没有同步。项目是独立的,客户端是线程安全的。
Callable<Response> externalCall(Item item) {
  return new Callable<Response>() {
    //...
  }
}
List<Future<Response>> futures = new ArrayList<>();
for(Item item : items){
    if(item is external){
        futures.add(executorService.submit(externalCall(item)));
    }

    if(item is internal){
        futures.add(new AsyncResult(synchResponse));
    }
}