簡體   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