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