繁体   English   中英

如何实现或找到线程安全的CompletionService的等效项?

[英]How can I implement or find the equivalent of a thread-safe CompletionService?

我有一个运行在Tomcat容器中的简单Web服务,该容器本质上是多线程的。 在服务中出现的每个请求中,我都希望同时调用外部服务。 java.util.concurrent中的ExecutorCompletionService使我部分地在那里。 我可以为它提供一个线程池,它会执行我的并发调用,并且在准备好任何结果时都会通知我。

处理特定传入请求的代码可能如下所示:

void handleRequest(Integer[] input) {
    // Submit tasks
    CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(Executors.newCachedThreadPool());
    for (final Integer i : input) {
        completionService.submit(new Callable<Integer>() {
            public Integer call() {
                return -1 * i;
            }
        });
    }

    // Do other stuff...

    // Get task results
    try {
        for (int i = 0; i < input.size; i++) {
            Future<Integer> future = completionService.take();
            Integer result = future.get();
            // Do something with the result...
        }
    } catch (Exception e) {
        // Handle exception
    }
}

这应该可以正常工作,但效率很低,因为为每个传入请求分配了一个新的线程池。 如果我将CompletionService作为共享实例移出,则会遇到多个共享同一CompletionService和线程池的请求的线程安全问题。 当请求提交任务并获得结果时,它们获得的结果将与提交的结果不同。

因此,我需要的是一个线程安全的CompletionService,它使我可以在所有传入请求之间共享一个公共线程池。 当每个线程完成一项任务时,应通知传入请求的适当线程,以便它可以收集结果。

实现这种功能的最直接方法是什么? 我确信这种模式已经被应用了很多次。 我只是不确定这是否是Java并发库提供的东西,或者是否可以使用某些Java并发构建块轻松构建。

更新:我忘记提及的一个警告是,我希望在任何提交的任务完成后立即得到通知。 这是使用CompletionService的主要优势,因为它可以使任务和结果的生产与使用分离。 我实际上并不关心返回结果的顺序,并且我想避免在等待结果按顺序返回时不必要地阻塞。

您共享Executor但不共享CompletionService

我们有一个AsyncCompleter可以做到这一点并处理所有簿记,使您能够:

Iterable<Callable<A>> jobs = jobs();
Iterable<A> results async.invokeAll(jobs);

results按返回顺序迭代并阻塞,直到有结果可用为止

您可以只使用普通的共享ExecutorService。 每当您提交任务时,您都将获得刚刚提交的任务的未来。 您可以将它们全部存储在列表中,以后再查询。

例:

private final ExecutorService service = ...//a single, shared instance

void handleRequest(Integer[] input) {
    // Submit tasks
    List<Future<Integer>> futures = new ArrayList<Future<Integer>>(input.length);
    for (final Integer i : input) {
        Future<Integer> future = service.submit(new Callable<Integer>() {
            public Integer call() {
                return -1 * i;
            }
        });
        futures.add(future);
    }

    // Do other stuff...

    // Get task results
    for(Future<Integer> f : futures){
        try {
            Integer result = f.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

java.util.concurrent提供了您所需的一切。 如果我正确理解您的问题,则您具有以下要求:

您要提交请求,并立即(在合理范围内)处理请求结果(响应)。 好吧,我相信您已经看到了解决问题的方法:java.util.concurrent.CompletionService。

简单来说,此服务将Executor和BlockingQueue组合在一起以处理Runnable和/或Callable任务。 BlockingQueue用于保留已完成的任务,您可以让另一个线程等待,直到已完成的任务在CompletionService对象上排队(通过调用take()完成)。

如前所述,共享执行器,并为每个请求创建CompletionService。 这似乎是一件昂贵的事情,但请再次考虑一下CS只是与Executor和BlockingQueue协作。 由于您共享了要实例化的最昂贵的对象,即Executor,我认为您会发现这是非常合理的成本。

但是...说了这么多,您似乎仍然有一个问题,这个问题似乎是请求处理与响应处理的分离。 这可以通过创建单独的服务来解决,该服务专门处理所有请求或特定类型请求的响应。

这是一个示例:(注意:这意味着Request对象实现的Callable接口应该返回Response类型...详细信息,此简单示例已将其省略)。

class RequestHandler {

  RequestHandler(ExecutorService responseExecutor, ResponseHandler responseHandler) {      
    this.responseQueue = ...
    this.executor = ...
  }  

  public void acceptRequest(List<Request> requestList) {

    for(Request req : requestList) {

      Response response = executor.submit(req);
      responseHandler.handleResponse(response);

    }  
  }  
}

class ResponseHandler {
  ReentrantLock lock;
  ResponseHandler(ExecutorService responseExecutor) {
    ...
  }

  public void handleResponse(Response res) {
    lock.lock() {
    try {
      responseExecutor.submit( new ResponseWorker(res) );
    } finally {
      lock.unlock();
    }    
  }

  private static class ResponseWorker implements Runnable {

    ResponseWorker(Response response) {
      response = ...
    }

    void processResponse() {         
      // process this response 
    }

    public void run() {      
      processResponse();      
    }  
  }
}

需要记住的两件事:一个,ExecutorService从阻塞队列中执行Callables或Runnable。 您的RequestHandler会接收任务的任务,并将这些任务排入执行器,并尽快进行处理。 您的ResponseHandler中也会发生同样的事情; 收到响应,并且该SEPARATE执行程序将尽快处理该响应。 简而言之,您有两个执行程序同时工作:一个在Request对象上,另一个在Response对象上。

为什么需要CompletionService

每个线程可以简单地提交或调用ExecutorService的“常规”共享实例上的Callables 然后,每个线程都保留自己的私有Future引用。

同样, Executor及其后代在设计上也是线程安全的。 您真正想要的是每个线程可以创建自己的任务并检查其结果。

java.util.concurrent的Javadoc非常出色; 它包括用法模式和示例。 阅读有关ExecutorService和其他类型的文档,以更好地了解如何使用它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM