J2EE 环境 - 多线程 - 从不同的服务中并行获取数据

J2EE environment- Multithreading - Fetching data from different services in parallel

对于我在下面使用的内容,是否有更好的替代方法(design/performance/memory 优化):

问题陈述:在 J2EE 环境中,当请求到来时,我的控制器需要从三个不同的服务获取数据(例如,第一个获取天气数据,第二个获取交通密度,最后一个获取当前没有外星人访问我们).如果一项服务需要 5 秒(最好的情况)来获取,那么以同步方式它们至少需要 15 秒。但控制器不应超过 6-7 秒。所以最终调用不同的线程。但可能存在服务花费超过 10 秒的情况(可能是最坏的情况)。

  1. So I thought of creating a thread wait, notify using an observable pattern with a small state maintaining. It was working, but not happy with the design.

  2. Then I found Java 'ForkJoinPool'. For each request controller creates a new 'ForkJoinPool' instance - now not creating it on each request. Created a custom class (say ForkService) extending RecursiveAction which has a List<RecursiveAction>. And ForkService's compute method has

@Override
    protected void compute() {
        for (RecursiveAction recursiveAction : actions) {
            recursiveAction.fork(); // async calling of all other service's compute method which actually fetches the data from the relevant service.
        }

        mPool.shutdown();
    }

目前正在使用,效果不错。想知道有没有更好的方法来做到这一点?

一个简单优雅的方法是使用固定的线程池和Guava的ListenableFuture on which you can call Futures.successfulAsList:

private MyResult getResult(MyRequest request) {
    ExecutorService es = Executors.newFixedThreadPool(3);
    ListeningExecutorService les = MoreExecutorslisteningDecorator(es);

    ListenableFuture<?> lf1 = les.submit(getCallableForService1(request));
    ListenableFuture<?> lf2 = les.submit(getCallableForService2(request));
    ListenableFuture<?> lf3 = les.submit(getCallableForService3(request));
    ListenableFuture<List<?>> lfs = Futures.successfulAsList(lf1, lf2, lf3);

    // wait 7 sec for results
    List<?> res = lfs.get(7, TimeUnit.SEONDS);

    return extractRes(res);
}

您当然应该为 Callable 处理正确的类型。

我对 Fork/Join framework 不太熟悉,但我可以马上告诉您,为每个请求创建一个新的线程池会破坏线程池的一半用途。线程的创建和销毁成本很高,使用线程池以便您可以重用线程。

查看文档,似乎 Fork/Join 旨在解决本质上递归且需要密集处理的问题。您的问题似乎不符合这两个条件。

您最好使用常规 ThreadPoolExecutor that's shared by all of your requests. The invokeAll 方法可能会特别有用;您可以一次提交所有 3 个请求,收到 List<Future<T>>,如果您愿意,甚至可以提供超时。

要获得结果,您可以简单地遍历 List<Future<Thing>> things:

Thing[] results = new Thing[3];
for (int i = 0; i < things.size(); ++i) {
    results[i] = things.get(i).get();
}

好久没写了,想着写点别的可以的:
1.回调的使用。当线程完成工作时,它会通过回调进行响应。
2.通知:每个线程发送工作完成通知。
3.问题中提到的RecursiveAction的使用
4. 使用 Guava 库的 ListenableFuture 的答案被接受为最终答案。