Java 8 Completable Futures allOf不同的数据类型

时间:2017-02-27 15:27:05

标签: java-8 completable-future

我有3个CompletableFutures,所有3个返回不同的数据类型。

我希望创建一个结果对象,它是所有3个期货返回的结果的组合。

所以我目前的工作代码如下:

public ClassD getResultClassD() {

    ClassD resultClass = new ClassD();
    CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA() );
    CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB() );
    CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC() );

    CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
                     .thenAcceptAsync(it -> {
                        ClassA classA = classAFuture.join();
                        if (classA != null) {
                            resultClass.setClassA(classA);
                        }

                        ClassB classB = classBFuture.join();
                        if (classB != null) {
                            resultClass.setClassB(classB);
                        }

                        ClassC classC = classCFuture.join();
                        if (classC != null) {
                            resultClass.setClassC(classC);
                        }

                     });

    return resultClass;
}

我的问题是:

  1. 我的假设是,由于我使用的是allOfthenAcceptAsync,因此此次通话将无阻塞。我的理解是对的吗?

  2. 这是处理返回不同结果类型的多个期货的正确方法吗?

  3. ClassD内构建thenAcceptAsync对象是否正确?

  4. 在thenAcceptAsync lambda中使用joingetNow方法是否合适?

4 个答案:

答案 0 :(得分:9)

您的尝试正朝着正确的方向发展,但不正确。您的方法getResultClassD()返回一个已经实例化的ClassD类型的对象,任意线程将在该对象上调用修改方法,而getResultClassD()调用者不会注意到。这可能会导致竞争条件,如果修改方法本身不是线程安全的,那么调用者永远不会知道ClassD实例何时可以使用。

正确的解决方案是:

public CompletableFuture<ClassD> getResultClassD() {

    CompletableFuture<ClassA> classAFuture
        = CompletableFuture.supplyAsync(() -> service.getClassA() );
    CompletableFuture<ClassB> classBFuture
        = CompletableFuture.supplyAsync(() -> service.getClassB() );
    CompletableFuture<ClassC> classCFuture
        = CompletableFuture.supplyAsync(() -> service.getClassC() );

    return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
         .thenApplyAsync(dummy -> {
            ClassD resultClass = new ClassD();

            ClassA classA = classAFuture.join();
            if (classA != null) {
                resultClass.setClassA(classA);
            }

            ClassB classB = classBFuture.join();
            if (classB != null) {
                resultClass.setClassB(classB);
            }

            ClassC classC = classCFuture.join();
            if (classC != null) {
                resultClass.setClassC(classC);
            }

            return resultClass;
         });
}

现在,getResultClassD()的调用者可以使用返回的CompletableFuture查询进度状态或链依赖操作,或者在操作完成后使用join()检索结果。< / p>

要解决其他问题,是的,此操作是异步的,并且在lambda表达式中使用join()是合适的。 join之所以被创建,是因为Future.get()被声明为抛出已检查的异常,会使这些lambda表达式中的使用变得不必要。

请注意,null测试仅在这些service.getClassX()实际可以返回null时才有用。如果其中一个服务调用因异常而失败,则整个操作(由CompletableFuture<ClassD>表示)将异常完成。

答案 1 :(得分:3)

我的回答与@Holger在答案中所做的类似,但将服务调用包装在Optional中,这导致thenApplyAsync阶段的代码更清晰

CompletableFuture<Optional<ClassA>> classAFuture
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassA())));

CompletableFuture<Optional<ClassB>> classBFuture
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassB()));

CompletableFuture<Optional<ClassC>> classCFuture
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassC()));

return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
     .thenApplyAsync(dummy -> {
        ClassD resultClass = new ClassD();

        classAFuture.join().ifPresent(resultClass::setClassA)
        classBFuture.join().ifPresent(resultClass::setClassB)
        classCFuture.join().ifPresent(resultClass::setClassC)

        return resultClass;
     });

答案 2 :(得分:2)

我遇到了类似的情况,并创建了一个简短的演示来展示如何解决此问题。

与@Holger类似的概念,除了我使用了一个函数来组合每个单独的未来。

https://github.com/te21wals/CompletableFuturesDemo

本质上:

    public class CombindFunctionImpl implement CombindFunction {
    public ABCData combind (ClassA a, ClassB b, ClassC c) {
        return new ABCData(a, b, c);
   }
}

...

    public class FutureProvider {
public CompletableFuture<ClassA> retrieveClassA() {
    return CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ClassA();
    });
}

public CompletableFuture<ClassB> retrieveClassB() {
    return CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ClassB();
    });
}
public CompletableFuture<ClassC> retrieveClassC() {
    return CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ClassC();
    });
}
}

......

public static void main (String[] args){
    CompletableFuture<ClassA> classAfuture = futureProvider.retrieveClassA();
    CompletableFuture<ClassB> classBfuture = futureProvider.retrieveClassB();
    CompletableFuture<ClassC> classCfuture = futureProvider.retrieveClassC();

    System.out.println("starting completable futures ...");
    long startTime = System.nanoTime();

    ABCData ABCData = CompletableFuture.allOf(classAfuture, classBfuture, classCfuture)
            .thenApplyAsync(ignored ->
                    combineFunction.combind(
                            classAfuture.join(),
                            classBfuture.join(),
                            classCfuture.join())
            ).join();

    long endTime = System.nanoTime();
    long duration = (endTime - startTime);
    System.out.println("completable futures are complete...");
    System.out.println("duration:\t" + Duration.ofNanos(duration).toString());
    System.out.println("result:\t" + ABCData);
}

答案 3 :(得分:1)

如果你不想申报多少变量,另一种处理方法是使用thenCombine或thenCombineAsync将你的未来连在一起。

public CompletableFuture<ClassD> getResultClassD()
{
  return CompletableFuture.supplyAsync(ClassD::new)
    .thenCombine(CompletableFuture.supplyAsync(service::getClassA), (d, a) -> {
      d.setClassA(a);
      return d;
    })
    .thenCombine(CompletableFuture.supplyAsync(service::getClassB), (d, b) -> {
      d.setClassB(b);
      return d;
    })
    .thenCombine(CompletableFuture.supplyAsync(service::getClassC), (d, c) -> {
      d.setClassC(c);
      return d;
    });
}

getter仍将以异步方式触发,并按顺序执行结果。它基本上是另一种语法选项,可以获得相同的结果。