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

 ̄綄美尐妖づ 提交于 2019-12-06 05:19:33

You share the Executor but not the CompletionService.

We have an AsyncCompleter that does exactly this and handles all the bookkeeping, allowing you to:

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

results iterates in order of return and blocks until a result is available

You can just use a normal shared ExecutorService. Whenever you submit a task, you will get a Future back for the task you just submitted. You can store all of them in a list and query them later.

Example:

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 provides everything you need. If I understand your question correctly, you have the following requirements:

You want to submit requests, and immediately (within reason) process the request result (Response). Well, I believe you've already seen the solution to your problem: java.util.concurrent.CompletionService.

This service which, rather simply, combines an Executor and a BlockingQueue to process Runnable and/or Callable tasks. The BlockingQueue is used to hold completed tasks, which you can have another thread wait on until a completed task is queued (this is accomplished by calling take()) on the CompletionService object.

As previous posters have mentioned, share the Executor, and create a CompletionService per request. This may seem like an expensive thing to do, but consider again that the CS is simply collaborating with the Executor and a BlockingQueue. Since you are sharing the most expensive object to instantiate, i.e., the Executor, I think you'll find that this a very reasonable cost.

However... all this being said, you still seem to have an issue, and that issue seems to be the separation of Request handling, from the handling of Responses. This might be approached by creating a separate service which exclusively handles Responses for all Requests, or for a certain type of Request.

Here is an example: (Note: it's implied that the Request object implement's the Callable interface which should return a Response type... details which I've omitted for this simple example).

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();      
    }  
  }
}

A couple of things to remember: one, an ExecutorService executes Callables or Runnables from a blocking queue; your RequestHandler receives task's, and those are enqueued on the Executor, and processed ASAP. The same thing happens in your ResponseHandler; a response is received, and as soon as that SEPARATE executor can, it will process that response. In short, you've got two executors working simultaneously: one on Request objects, the other on Response objects.

Why do you need a CompletionService?

Each thread could simply submit to or invoke Callables on a "regular" and shared instance of an ExecutorService. Each thread then holds on to their own private Future references.

Also, Executor and its descendants are thread-safe by design. What you actually want is that each thread can create its own tasks and inspect their results.

The Javadoc in java.util.concurrent is excellent; it includes usage patterns and examples. Read the doc for ExecutorService and other types to better understand how to use them.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!