异步方法后跟Java 8中并行执行的方法

时间:2018-05-07 14:53:36

标签: java concurrency java-8 java.util.concurrent

在花了一天学习java Concurrency API之后,我仍然不知道如何在CompletableFuture和ExecutorService类的帮助下创建以下功能:

当我在我的REST端点上收到请求时,我需要:

  1. 启动异步任务(包括数据库查询,过滤等),最后会给我一个字符串网址列表
  2. 同时,使用HTTP OK回复REST调用者,收到请求,我正在处理它
  3. 异步任务完成后,我需要将HTTP请求(有效负载,REST调用者给我)发送到我从作业中获取的URL。最多URL的数量大约是100,所以我需要这些并行发生。
  4. 理想情况下,我有一些同步计数器可以计算有多少http请求是成功/失败的,我可以将这些信息发送回REST调用者(我需要将其发回的URL在请求有效负载内提供) )。
  5. 我已经为这些编写了构建块(方法如:getMatchingObjectsFromDB(callerPayload),getURLs(resultOfgetMachingObjects),sendHttpRequest(url,methodType)等等)),我只是无法弄清楚如何绑定第1步和第3步一起。我会在步骤1中使用CompletableFuture.supplyAsync(),然后我需要CompletableFuture.thenComponse方法来启动第3步,但我不清楚如何使用此API进行并行操作。但是ExecutorService executor = Executors.newWorkStealingPool();非常直观,它根据可用的处理能力来创建一个线程池,并且可以通过invokeAll()方法提交任务。

    如何一起使用CompletableFutureExecutorService?或者我如何保证CompletableFuture并行执行任务列表?展示代码片段将非常感激。感谢。

2 个答案:

答案 0 :(得分:2)

您应该使用join()等待所有线程完成。

创建Map<String, Boolean> result以存储您的请求结果。

在您的控制器中:

public void yourControllerMethod() {

  CompletableFuture.runAsync(() -> yourServiceMethod());
}

在您的服务中:

// Execute your logic to get List<String> urls

List<CompletableFuture> futures = urls.stream().map(v -> 
 CompletableFuture.supplyAsync(url -> requestUrl(url))
           .thenAcceptAsync(requestResult -> result.put(url, true or false))
).collect(toList()); // You have list of completeable future here

然后使用.join()等待所有线程(请记住,您的服务已在其自己的线程中执行)

CompletableFuture.allOf(futures).join();

然后,您可以通过访问result地图

来确定哪一个成功/失败

修改

请发布您的程序代码,以便其他人也可以理解您。

我已阅读您的代码,以下是必要的修改:

  

当没有注释掉这个for循环时,接收器webserver得到了   相同的请求两次,         我不明白这个for循环的目的。

对不起我以前的回答,我没有清理它。这只是我头脑中的一个暂时的想法,我忘了在最后删除:D

只需将其从代码中删除

即可
  

// allOf()只接受数组,因此需要转换List       / *代码永远不会超过这一部分(我知道allOf()是一个阻塞调用),即使在接收者获得HTTP请求之后很久

   with the correct payload. I'm not sure yet where exactly the code gets stuck */

您的地图应该是ConcurrentHashMap,因为您稍后会同时修改它。

Map<String, Boolean> result = new ConcurrentHashMap<>();

如果您的代码仍未按预期运行,我建议您删除parallelStream()部分。

CompletableFutureparallelStream使用公共forkjoin池。我认为游泳池已经用尽了。

您应该为CompletableFuture

创建自己的游泳池
Executor pool = Executors.newFixedThreadPool(10);

使用该池执行您的请求:

CompletableFuture.supplyAsync(YOURTASK, pool).thenAcceptAsync(Yourtask, pool)

答案 1 :(得分:0)

为了完成,这里是清理和测试后代码的相关部分(感谢MạnhQuyếtNguyễn):

休息控制器类:

@POST
@Path("publish")
public Response publishEvent(PublishEvent eventPublished) {
    /*
        Payload verification, etc.
    */

    //First send the event to the right subscribers, then send the resulting hashmap<String url, Boolean subscriberGotTheRequest> back to the publisher
    CompletableFuture.supplyAsync(() -> EventHandlerService.propagateEvent(eventPublished)).thenAccept(map -> {
      if (eventPublished.getDeliveryCompleteUri() != null) {
        String callbackUrl = Utility
            .getUri(eventPublished.getSource().getAddress(), eventPublished.getSource().getPort(), eventPublished.getDeliveryCompleteUri(), isSecure,
                    false);
        try {
          Utility.sendRequest(callbackUrl, "POST", map);
        } catch (RuntimeException e) {
          log.error("Callback after event publishing failed at: " + callbackUrl);
          e.printStackTrace();
        }
      }
    });

    //return OK while the event publishing happens in async
    return Response.status(Status.OK).build();
}

服务类:

private static List<EventFilter> getMatchingEventFilters(PublishEvent pe) {
    //query the database, filter the results based on the method argument
}

private static boolean sendRequest(String url, Event event) {
    //send the HTTP request to the given URL, with the given Event payload, return true if the response is positive (status code starts with 2), false otherwise
}

static Map<String, Boolean> propagateEvent(PublishEvent eventPublished) {
    // Get the event relevant filters from the DB
    List<EventFilter> filters = getMatchingEventFilters(eventPublished);
    // Create the URLs from the filters
    List<String> urls = new ArrayList<>();
    for (EventFilter filter : filters) {
      String url;
      try {
        boolean isSecure = filter.getConsumer().getAuthenticationInfo() != null;
        url = Utility.getUri(filter.getConsumer().getAddress(), filter.getPort(), filter.getNotifyUri(), isSecure, false);
      } catch (ArrowheadException | NullPointerException e) {
        e.printStackTrace();
        continue;
      }
      urls.add(url);
    }

    Map<String, Boolean> result = new ConcurrentHashMap<>();
    Stream<CompletableFuture> stream = urls.stream().map(url -> CompletableFuture.supplyAsync(() -> sendRequest(url, eventPublished.getEvent()))
                                                                                 .thenAcceptAsync(published -> result.put(url, published)));
    CompletableFuture.allOf(stream.toArray(CompletableFuture[]::new)).join();
    log.info("Event published to " + urls.size() + " subscribers.");
    return result;
}

调试这比平常有点困难,有时代码只是神奇地停止了。为了解决这个问题,我只将代码部分放入异步任务中,这是绝对必要的,我确保任务中的代码使用了线程安全的东西。我起初也是一个愚蠢的人,我EventHandlerService.class内的方法使用了synchronized关键字,导致Service class方法中的CompletableFuture没有执行,因为它默认使用一个线程池。

  

标记为synchronized的逻辑变为同步块,在任何给定时间只允许一个线程执行。