Java 我如何实现或找到线程安全CompletionService的等价物?
我在Tomcat容器中运行了一个简单的web服务,它本质上是多线程的。在进入服务的每个请求中,我希望对外部服务进行并发调用。java.util.concurrent中的ExecutorCompletionService让我部分了解了这一点。我可以为它提供一个线程池,它将负责执行我的并发调用,并在任何结果就绪时通知我 处理特定传入请求的代码可能如下所示:Java 我如何实现或找到线程安全CompletionService的等价物?,java,concurrency,executorservice,Java,Concurrency,Executorservice,我在Tomcat容器中运行了一个简单的web服务,它本质上是多线程的。在进入服务的每个请求中,我希望对外部服务进行并发调用。java.util.concurrent中的ExecutorCompletionService让我部分了解了这一点。我可以为它提供一个线程池,它将负责执行我的并发调用,并在任何结果就绪时通知我 处理特定传入请求的代码可能如下所示: void handleRequest(Integer[] input) { // Submit tasks Completion
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
}
}
void handleRequest(整数[]输入){
//提交任务
CompletionService CompletionService=new ExecutionCompletionService(Executors.newCachedThreadPool());
for(最终整数i:输入){
completionService.submit(新的可调用(){
公共整数调用(){
返回-1*i;
}
});
}
//做其他事情。。。
//获取任务结果
试一试{
for(int i=0;i
这应该可以很好地工作,但是效率很低,因为每个传入请求都分配了一个新的线程池。如果将CompletionService作为共享实例移出,我将遇到线程安全问题,多个请求共享同一CompletionService和线程池。当请求提交任务并得到结果时,它们得到的结果不是它们提交的结果
因此,我需要一个线程安全的CompletionService,它允许我在所有传入请求之间共享一个公共线程池。当每个线程完成一项任务时,应该通知传入请求的相应线程,以便它可以收集结果
实现这种功能最直接的方法是什么?我相信这种模式已经应用了很多次;我只是不确定这是否是Java并发库提供的,或者是否可以使用一些Java并发构建块轻松构建
更新:我忘记提到的一个警告是,我希望在完成任何提交的任务后尽快收到通知。这是使用CompletionService的主要优势,因为它将任务和结果的生产和消费解耦。实际上,我并不关心返回结果的顺序,我希望在等待结果按顺序返回时避免不必要的阻塞。为什么需要
CompletionService
每个线程可以简单地提交或调用ExecutorService
的“常规”共享实例上的Callables
。然后,每个线程都保留自己的私有将来的引用
而且,Executor
及其子代在设计上是线程安全的。实际上,您需要的是每个线程都可以创建自己的任务并检查其结果
java.util.concurrent
中的Javadoc非常优秀;它包括使用模式和示例。阅读文档中的和其他类型,以更好地了解如何使用它们。您可以使用普通的共享服务。无论何时提交任务,您都将获得刚刚提交的任务的未来回报。您可以将它们全部存储在一个列表中,稍后再查询它们
例如:
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();
}
}
}
private final executor service=…//单个共享实例
void handleRequest(整数[]输入){
//提交任务
列表未来=新的ArrayList(input.length);
for(最终整数i:输入){
Future=service.submit(new Callable()){
公共整数调用(){
返回-1*i;
}
});
期货。添加(期货);
}
//做其他事情。。。
//获取任务结果
for(未来f:未来){
试一试{
整数结果=f.get();
}捕获(例外e){
e、 printStackTrace();
}
}
}
您共享的是执行器
,而不是完成服务
我们有一个可以做到这一点并处理所有簿记工作的系统,允许您:
Iterable<Callable<A>> jobs = jobs();
Iterable<A> results async.invokeAll(jobs);
Iterable jobs=jobs();
Iterable结果async.invokeAll(作业);
结果
按返回和块的顺序迭代,直到结果可用java.util.concurrent提供您所需的一切。如果我正确理解您的问题,您有以下要求:
您希望提交请求,并立即(在合理范围内)处理请求结果(响应)。我相信您已经看到了问题的解决方案:java.util.concurrent.CompletionService
此服务相当简单,它结合了执行器和阻塞队列来处理可运行和/或可调用的任务。BlockingQueue用于保存已完成的任务,您可以让另一个线程等待,直到完成的任务在CompletionService对象上排队(这是通过调用take()完成的)
正如前面的海报所提到的,共享Executor,并为每个请求创建CompletionService。这看起来像是一件昂贵的事情,但是请再次考虑CS只是与执行器和阻塞队列协作。由于您共享的是要实例化的最昂贵的对象,即执行器,因此我认为您会发现这是一个非常合理的成本
然而。。。尽管如此,您似乎仍然有一个问题,这个问题似乎是请求处理与响应处理的分离。这可以通过创建一个单独的服务来实现,该服务专门处理所有请求或特定类型请求的响应
以下是一个例子:
(注意:这意味着请求对象实现的是
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();
}
}
}