[英]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.