67202


Java8,非同期処理の勉強をしています。  
以下のコードで出力結果の期待値は「200」です。

ただし、200がでたり、でなかったりします。  
**その理由を教えて下さい。**

以下のリンク先からコードは取得しました。  
<https://qiita.com/subaru44k/items/d98ad79d21abccedb20b>

出力処理である「thenAcceptAsync」が行われないことがあるとは考えづらいのですが、  
なぜ出力されない時があるのか知りたいです。

ある値を返すのはSupplierで、変換はFunctionで、それを受け取って処理をするのはConsumerで実施する。という認識です。

よろしくお願いいたします。

[code]

    package java8;
    
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutionException;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    public class CompletableFutureTest3 {
    
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            Supplier<Integer> initValueSupplier = () -> 100;
            Function<Integer, Integer> multiply = value -> value * 2;
            Consumer<Integer> valueConsumer = value -> System.out.println(value);
    
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(initValueSupplier)
                    .thenApplyAsync(multiply)
                    .thenAcceptAsync(valueConsumer);
        }
    
    }
    
[/code]


CC BY-SA 4.0
2020-06-01T23:03:08.223
67200
2020-06-02T21:21:57.503
40231
question
java java8
非同期処理において結果が思ったように出ない理由を知りたい


非同期処理の完了を待たずに`main`メソッドが終了してしまうためでしょう。

`main`メソッド終了後、プロセスの終了処理が完了する前に`System.out.println(value)`の出力が完了していれば、コンソール等に結果が出力されますが、それが間に合わないと結果が何も(場合によっては一部しか)表示されないと言うことになります。

極めてわずかなタイミングの差なので、ほんの1msecだけ`main`の完了を遅らせてやれば、確実に出力される(*)ようになるはずです。

[code]

        public static void main(String[] args) throws InterruptedException, ExecutionException {
            Supplier<Integer> initValueSupplier = () -> 100;
            Function<Integer, Integer> multiply = value -> value * 2;
            Consumer<Integer> valueConsumer = value -> System.out.println(value);
    
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(initValueSupplier)
                    .thenApplyAsync(multiply)
                    .thenAcceptAsync(valueConsumer);
    
            Thread.sleep(1); //<- `main`の完了を1msec遅らせる
        }
    
[/code]

(*)「確実に出力される」のは、この問題に限っての話で、実際に非同期処理の終了を確実に完了するのを待つ場合には、yohjp
さんのコメントなども参考にしてください。


CC BY-SA 4.0
2020-06-01T23:59:10.060
67202
2020-06-02T00:42:12.843
2020-06-02T00:42:12.843
13972
13972
67200
answer


> 出力処理である「thenAcceptAsync」が行われないことがあるとは考えづらいのですが、  
>  なぜ出力されない時があるのか知りたいです。

`thenAcceptAsync`は確かに(メインスレッドで同期的に)実行されます。ただしこれは、言わば「実行の予約」であり、実際に実行されるかどうかは別の話です。

* * *

[`CompletableFuture#supplyAsync()`の説明](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html#supplyAsync\(java.util.function.Supplier\))にある通り、ここで登場する一連の
`CompletableFuture` は
[`ForkJoinPool`](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html#commonPool\(\))
executor service が実行します。  
そして[`ForkJoinPool`のワーカースレッドはデーモンスレッドです](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html)。

> すべてのワーカー・スレッドは、Thread.isDaemon() set trueで初期化されます。

ここで、「デーモンスレッド」についての説明は[`Thread`のJavadoc](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Thread.html#setDaemon\(boolean\))にあります:

> Java仮想マシンは、実行中のスレッドがデーモン・スレッドだけになると終了します。

さて、今回のコードを実行する上で登場するスレッドは2種類あります

  * メインスレッド
  * `ForkJoinPool` executor serviceのワーカースレッド

が、このうち非デーモンスレッドは前者メインスレッドだけであり、前述の通り後者はデーモンスレッドです。  
つまり、メインスレッドが終了すれば「実行の予約」が存在しようとも実行されないままJVMは終了します。

(`ForkJoinPool`からすれば、実行完了まで待ちたければ[`join()`](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html#join\(\))すればええやん、結果を受け取らないってことは実行してもせんでもええんやろ？ということですね)

* * *

ちなみに、[`Executors.newFixedThreadPool()`](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/Executors.html#newFixedThreadPool\(int\))なんかで作成されるexecutor
serviceのワーカスレッドは、[デフォルトでは非デーモンスレッドです](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/Executors.html#defaultThreadFactory\(\))。  
なので、

[code]

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ...
    
        // ExecutorService es = ForkJoinPool.commonPool();
        ExecutorService es = Executors.newFixedThreadPool(1);
    
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(initValueSupplier, es)
            .thenApplyAsync(multiply, es)
            .thenAcceptAsync(valueConsumer, es);
    
    }
    
[/code]

といったふうに `ForkJoinPool.commonPool()` を `Executors.newFixedThreadPool()`
で挿げ替えることでデーモンスレッド/非デーモンスレッドの違いが確認できるかと思います。

この場合も2種類のスレッド

  * メインスレッド
  * (`Executors.newFixedThreadPool()`によって生成される) `ThreadPoolExecutor` executor serviceのワーカスレッド

が登場することになりますが、今回はワーカスレッドも非デーモンスレッドであるためメインスレッドが終了してもJVMは終了しません(ので、結果的にタスクの実行は必ず実行されることになります)。


CC BY-SA 4.0
2020-06-02T07:12:15.833
67221
2020-06-02T21:21:57.503
2020-06-02T21:21:57.503
2808
2808
67200
answer