I need to call a few (less than 30) REST APIs concurrently, and immediately return the first response that I get from those API calls.
I implemented a method like this:
public String fetchFastest(List<URL> urls) throws ExecutionException, InterruptedException {
var executor = Executors.newFixedThreadPool(urls.size());
var futures = new ArrayList<Future<String>>();
urls.forEach(url -> {
futures.add(
executor.submit(() -> getResponse(url)) // performs a http request with timeout and returns the response (or null in the case of failure)
);
});
var failed = new ArrayList<Future<String>>();
while (failed.size() < futures.size()) {
for(var f: futures) {
if(f.isDone() && !failed.contains(f)) {
if(f.get() == null) {
failed.add(f);
}
else {
executor.shutdownNow();
return f.get();
}
}
}
}
throw new RuntimeException("No response.");
}
This approach seems to be working. However, given that this method is called frequently, and the fact that creating Thread Pools is an expensive task (and I am creating one with each method call), I wonder whether I can somehow improve this approach or find other solutions that are faster and have less computational cost.
Any suggestions are welcome. Thank you in advance.
CodePudding user response:
First of all, use executor.invokeAny()
. It will wait until one of the tasks is successfully completed and cancel the rest (so will replace almost all your code above).
About creating thread pool on every method call - sometimes it makes sense. But at the beginning, I would start with the common pool (ForkJoinPool.commonPool()
) or create your own static (==reusable) pool and only replace it with pool-per-method-call after analyzing performance.
CodePudding user response:
Optimization 1: Create a static FixedThreadPool.
You can determine the thread-pool size by: (No. of concurrent calls to the fetchFastest method) x (urls.size())
This is better as the GC will not have clean the thread-pool instances that you created on each function call and the the static FixedThreadPool will be re-used.
Optimization 2: Interrupt threads that are no longer needed.
Interrupt all other threads once you get the first response. This is will save CPU cycles on busy wait and compute(if any).