CompletableFuture - エレガントな非同期コードの書き方

序文

私たちの意識では、プログラムの同期実行は、より多くの人々の考え方に沿ったものであるが、通常は良いことで非同期処理ではありません。コード中に分散された傾向が示さ処理エラーが発生することを特徴とする工程を必要に応じて非同期演算動作コールバックの場合、それは、互いに内側にネストされてもよい、状況は悪化します。含まれるJava 8を発表、多くの新機能、CompletableFutureの非同期コードを書くことは、私たちが容易になり、導入クラスは、クラスが50の以上の方法を含む、非常に強力ですが、読みやすいです。

CompletableFutureは何ですか

CompletableFutureクラス設計が触発されたListenableFutureを実装するクラスインタフェースおよび非同期プログラミング・モデルを強化するために、非ブロッキングコールバックメソッドを使用することにより、ラムダをサポートする多くの方法を、追加します。それは、私たちは、タスクを実行するために、別のスレッド(すなわち非同期)、および書き込み非ブロックコードにメインスレッドの通知タスクの完了または失敗の進行に伴って、メインアプリケーションスレッドに渡すことができます。Google GuavaFutureCompletionStage

なぜCompletableFutureを紹介

Java1.5リリースの紹介Future、あなたは、単にプレースホルダの操作の結果として理解し、それを置くことができ、それは操作の結果を得るために2つのメソッドを提供します。

  • get():このメソッドは、スレッドが操作の結果を無期限に待機しますと呼ばれています。
  • get(long timeout, TimeUnit unit):このメソッドは、指定した時刻に、スレッドと呼ばれるtimeout待ちタイムアウトがスローされます場合は、内の結果を待つためにTimeoutException、例外を。

Futureあなたは使用することができるRunnableCallable、それはいくつかの問題が存在すると、タスクに提出インスタンスは、そのソースコードを見ることができます。

  • ブロッキング呼び出しget()方法を計算が完了するまで待機するまでブロックされます、それは任意の方法を提供していない完了を通知することができ、それはまた、付加機能コールバック関数を持っていません。
  • 連鎖呼び出し重合プロセスと結果、多くの場合、我々はより多くのリンクを希望Future長い計算を完了するには、結果を統合し、別のタスクに結果を送信するには、この時間の必要性は、インタフェースがこのプロセスを完了することは困難です。
  • 例外処理は、 Future例外処理のいずれかの方法を提供していません。

これらの問題はCompletableFuture、すでに、使用方法で見てみましょうを解決していますCompletableFuture

CompletableFutureを作成する方法

最も簡単な方法は、作成呼び出すことですCompletableFuture.completedFuture(U value)完成取得するメソッドをCompletableFutureオブジェクトを。

@Test
public void testSimpleCompletableFuture() {
    CompletableFuture<String> completableFuture = CompletableFuture.completedFuture("Hello mghio");
    assertTrue(completableFuture.isDone());
    try {
        assertEquals("Hello mghio", completableFuture.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

我々が完了しない場合という注意CompleteableFuture通話をgetする方法があるため、それはなりますFuture完了していないので、get呼び出しは、あなたが使用することができ、永久にブロックしますCompletableFuture.complete完了するために、手動の方法をFuture

非同期処理タスク

私たちは仕事を心配することなくバックグラウンドで非同期タスクを実行するプログラムの結果に対処したいときは、使用することができるrunAsync受信方法、Runnable型リターンのパラメータをCompletableFuture<Void>

@Test
public void testCompletableFutureRunAsync() {
    AtomicInteger variable = new AtomicInteger(0);
    CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> process(variable));
    runAsync.join();
    assertEquals(100, variable.get());
}

public void process(AtomicInteger variable) {
    System.out.println(Thread.currentThread() + " Process...");
    variable.set(100);
}

我々は、バックグラウンドでタスクを実行したいと非同期処理の結果は、仕事を得るために必要がある場合は、あなたが使用できるsupplyAsync方法を、この方法は、受信Supplier<T>復帰にパラメータの種類をCompletableFuture<T>

@Test
public void testCompletableFutureSupplyAsync() {
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process);
    try {
        assertEquals("Hello mghio", supplyAsync.get()); // Blocking
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

public String process() {
    return "Hello mghio";
} 

あなたは問題がある可能性があり、ここを参照してください、上記の実行runAsyncおよびsupplyAsyncねじ切り作業は、誰がそれを作成し、から来ているのですか?それは実際に8とJavaはparallelStream似て、CompletableFutureだけでなく、グローバルからForkJoinPool.commonPool()取得したこれらのタスクのスレッドを実行します。一方、上記の二つの方法はまた、あなたがを知るために行く場合は、実際には、タスクを実行するためのカスタム・スレッド・プールを提供しCompletableFuture、あなたがいることがわかります、ソースコードAPIのすべてのメソッドは、またはカスタムなしに、オーバーロードされたバージョンを持ってExecutor実行デバイス。

@Test
public void testCompletableFutureSupplyAsyncWithExecutor() {
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process, newFixedThreadPool);
    try {
        assertEquals("Hello mghio", supplyAsync.get()); // Blocking
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

public String process() {
    return "Hello mghio";
}

連鎖重合プロセスの呼び出しと結果

我々はことを知っている方法が残る結果にGET、まで提供しそしてこのような状況を回避するために他の方法が、タスクが完了した後、我々はまた、コールバック通知を追加することができます。これらのメソッドは、シナリオを使用し、次のとおりです。CompletableFutureget()阻塞CompletableFuturethenApplythenAcceptthenRun

  • thenApplyときに我々がからにしている場合Futureは、タスクの前に、カスタムコード値を受け取った後、ビジネスを実行し、このタスクにいくつかの値を返すとき、あなたがこの方法を使用することができます
  • thenAcceptからに私たちが望む場合はFuture、時間の結果は、戻り値を気にしないの前にカスタムタスクを実行するためにビジネス価値コードの番号を受け取った後、あなたがこの方法を使用することができます
  • thenRunは、私たちが今後のビジネスコードは、カスタムの実行が終了したい、このいずれかの戻り値を行うにはしたくない場合は、このメソッドを使用することができます
@Test
public void testCompletableFutureThenApply() {
    Integer notificationId = CompletableFuture.supplyAsync(this::thenApplyProcess)
        .thenApply(this::thenApplyNotify) // Non Blocking
        .join();
    assertEquals(new Integer(1), notificationId);
}

@Test
public void testCompletableFutureThenAccept() {
    CompletableFuture.supplyAsync(this::processVariable)
        .thenAccept(this::thenAcceptNotify) // Non Blocking
        .join();
    assertEquals(100, variable.get());
}

@Test
public void testCompletableFutureThenRun() {
    CompletableFuture.supplyAsync(this::processVariable)
        .thenRun(this::thenRunNotify)
        .join();
    assertEquals(100, variable.get());
}

private String processVariable() {
    variable.set(100);
    return "success";
}

private void thenRunNotify() {
    System.out.println("thenRun completed notify ....");
}

private Integer thenApplyNotify(Integer integer) {
    return integer;
}

private void thenAcceptNotify(String s) {
    System.out.println(
    String.format("Thread %s completed notify ....", Thread.currentThread().getName()));
}

public Integer thenApplyProcess() {
    return 1;
}

非同期計算の数が多いので、私たちは、連鎖呼び出しモードを使用して、別のコールバックからコールバックに価値を提供し続けることができれば、それは非常にシンプルな使用しています。

@Test
public void testCompletableFutureThenApplyAccept() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .thenAccept((i) -> notifyByEmail()).join();
}

private void notifyByEmail() {
    // business code
    System.out.println("send notify by email ...");
}

private Double notifyBalance(Double d) {
    // business code
    System.out.println(String.format("your balance is $%s", d));
    return 9527D;
}

private Double calculateBalance(Object o) {
    // business code
    return 9527D;
}

private Double findAccountNumber() {
    // business code
    return 9527D;
}

比較慎重な友人は、いくつかの方法のすべての以前の例では、すべてのメソッドが同じスレッドで実行されていることに気づいたかもしれません。私たちは、これらのタスクは別のスレッドで実行したい場合は、次に我々は、非同期バージョンに対応するこれらのメソッドを使用することができます。

@Test
public void testCompletableFutureApplyAsync() {
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
    ScheduledExecutorService newSingleThreadScheduledExecutor = Executors
        .newSingleThreadScheduledExecutor();
    CompletableFuture<Double> completableFuture =
        CompletableFuture
            .supplyAsync(this::findAccountNumber,
                newFixedThreadPool) // 从线程池 newFixedThreadPool 获取线程执行任务
            .thenApplyAsync(this::calculateBalance,
                newSingleThreadScheduledExecutor)
            .thenApplyAsync(this::notifyBalance);
    Double balance = completableFuture.join();
    assertEquals(9527D, balance);
}

実装プロセスの結果

thenComposeこのようなビジネス・コンピューティングの口座残高などのタスク依存の治療に適した方法、:まず、我々は、まず計算が通知を送信する前に完了し、アカウントを見つけ、その後、口座の残高を計算する必要があります。これらのタスクはすべて、前のジョブの戻りに依存しているCompletableFutureし、我々が使用する必要があり、結果thenComposeのJava 8ストリームと少し似て、実際には、方法をflatMap操作。

@Test
public void testCompletableFutureThenCompose() {
    Double balance = this.doFindAccountNumber()
        .thenCompose(this::doCalculateBalance)
        .thenCompose(this::doSendNotifyBalance).join();
    assertEquals(9527D, balance);
}

private CompletableFuture<Double> doSendNotifyBalance(Double aDouble) {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doSendNotifyBalance ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private CompletableFuture<Double> doCalculateBalance(Double d) {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doCalculateBalance ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private CompletableFuture<Double> doFindAccountNumber() {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doFindAccountNumber ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private void sleepSeconds(int timeout) {
    try {
        TimeUnit.SECONDS.sleep(timeout);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

thenCombineこの方法は、主に別のタスクの複数の処理結果を結合するために使用されます。その後、我々が使用することができ、私たちは人の名前と住所を検索する必要があると、あなたがそれぞれ取得するためにさまざまなタスクを使用することができ、その後、あなたは人の(名+アドレス)に関する完全な情報が必要な結果を取得するには、次の2つのアプローチをマージする必要があるthenCombine方法を。

@Test
public void testCompletableFutureThenCombine() {
    CompletableFuture<String> thenCombine = this.findName().thenCombine(this.findAddress(), (name, address) -> name + address);
    String personInfo = thenCombine.join();
    assertEquals("mghio Shanghai, China", personInfo);
}

private CompletableFuture<String> findAddress() {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "Shanghai, China";
    });
}

private CompletableFuture<String> findName() {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "mghio ";
    });
}

完全な複数のタスクを実行するのを待ちます

多くの場合、我々は、並行して複数のタスクを実行し、完了したすべてのタスクを処理して、いくつかを実行します。我々は、3人の異なるユーザー名を検索し、結果を結合するとします。あなたが使用することができます。この時点で、CompletableFuture静的メソッドallOfすべてのタスクが完了するまで、メソッドは待機し、この方法は、それが結果、我々は手動タスクの組み合わせを必要とするので、結果を組み合わせたすべてのタスクを返さないことに留意すべきです。

@Test
public void testCompletableFutureAllof() {
    List<CompletableFuture<String>> list = Lists.newArrayListWithCapacity(4);
    IntStream.range(0, 3).forEach(num -> list.add(findName(num)));

    CompletableFuture<Void> allFuture = CompletableFuture
        .allOf(list.toArray(new CompletableFuture[0]));

    CompletableFuture<List<String>> allFutureList = allFuture
        .thenApply(val -> list.stream().map(CompletableFuture::join).collect(Collectors.toList()));

    CompletableFuture<String> futureHavingAllValues = allFutureList
        .thenApply(fn -> String.join("", fn));

    String result = futureHavingAllValues.join();
    assertEquals("mghio0mghio1mghio2", result);
}

private CompletableFuture<String> findName(int num) {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "mghio" + num;
    });
} 

例外処理

実際には、例外は、マルチスレッドプログラムでは良い取引ではありませんが、幸いにCompletableFutureコード上の例では、例外を処理する非常に便利な方法を提供してくれました。

@Test
public void testCompletableFutureThenCompose() {
    Double balance = this.doFindAccountNumber()
        .thenCompose(this::doCalculateBalance)
        .thenCompose(this::doSendNotifyBalance).join();
}

上記コードは、三つの方法でdoFindAccountNumberdoCalculateBalanceそしてdoSendNotifyBalanceあれば、どのような異常が発生すると、次のメソッド呼び出しが実行されません。
CompletableFuture3つのそれぞれの例外を処理する方法、提供exceptionallyhandleおよびwhenComplete方法を。最初の方法は使用することですexceptionally以前の方法が失敗し、例外が例外コールバックが呼び出され、発生した場合、例外を処理する方法を。

@Test
public void testCompletableFutureExceptionally() {
    CompletableFuture<Double> thenApply = CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .exceptionally(ex -> {
            System.out.println("Exception " + ex.getMessage());
            return 0D;
        });
    Double join = thenApply.join();
    assertEquals(9527D, join);
}

第二の方法は、使用するhandleメソッドハンドル例外を、上記よりも例外を処理する方法はexceptionally、より柔軟な方法で、我々は現在の例外オブジェクトと処理結果の両方を取得することができます。

@Test
public void testCompletableFutureHandle() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .handle((ok, ex) -> {
            System.out.println("最终要运行的代码...");
            if (ok != null) {
            System.out.println("No Exception !!");
            } else {
            System.out.println("Exception " + ex.getMessage());
            return -1D;
            }
            return ok;
        });
}

三番目は使用することでwhenComplete異常を治療する方法。

@Test
public void testCompletableFutureWhenComplete() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .whenComplete((result, ex) -> {
            System.out.println("result = " + result + ", ex = " + ex);
            System.out.println("最终要运行的代码...");
        });
}

概要

本稿では、紹介しCompletableFutureた機能を提供することは非常に強力ですが、このクラスのメソッドおよび使用クラスメソッドの以下の部分を理解したり、詳細なソースにするための基本的な使い方に精通非同期プログラミングをより多く使用し、多くのことを分析と実装の原則。

おすすめ

転載: www.cnblogs.com/mghio/p/12650256.html