JavaRush /Java Blog /Random-JA /スレッドで Java を台無しにすることはできません: パート IV - Callable、Future、そして友...
Viacheslav
レベル 3

スレッドで Java を台無しにすることはできません: パート IV - Callable、Future、そして友人たち

Random-JA グループに公開済み

導入

最初の部分でスレッドがどのように作成されるかについてはすでに説明しました。もう一度思い出してみましょう。 スレッドで Java を台無しにすることはできません: パート IV - Callable、Future とその仲間たち - 1スレッドはThreadその中で実行されるものなrunので、tutorialspoint Java オンライン コンパイラーを使用して次のコードを実行しましょう。
public class HelloWorld {

    public static void main(String []args){
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
スレッドでタスクを実行するにはこれが唯一のオプションですか?

java.util.concurrent.Callable

java.lang.Runnableには兄弟がいて、彼の名前は java.util.concurrent.Callable で、Java 1.5 で生まれました。違いは何ですか? このインターフェースの JavaDoc を詳しく見ると、 とは異なり、新しいインターフェースは結果を返すRunnableメソッドを宣言していることがわかります。callまた、デフォルトでは例外がスローされます。つまり、try-catchチェックされた例外用のブロックを作成する必要がなくなります。もう悪くないですよね?Runnable代わりに新しいタスクが 追加されました。
Callable task = () -> {
	return "Hello, World!";
};
しかし、それをどうすればいいでしょうか?結果を返すタスクをスレッド上で実行する必要があるのはなぜでしょうか? 明らかに、将来的には、将来実行されるアクションの結果を受け取ることが期待されます。未来は英語で未来。そして、まったく同じ名前のインターフェイスがあります。java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Futureインターフェースは、結果を取得するためのメソッドやステータスをチェックするためのメソッドなど、将来的に結果を取得する予定のタスクを操作するための API を記述します。私たちはFutureその実装java.util.concurrent.FutureTaskに興味があります。つまりTask、これは で実行されるものですFuture。この実装で興味深いのは、 と を実装していることですRunnable。これは、スレッドでタスクを処理する古いモデルと新しいモデル (Java 1.5 で登場したという意味で新しい) のアダプターの一種と考えることができます。以下に例を示します。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class HelloWorld {

    public static void main(String []args) throws Exception {
        Callable task = () -> {
            return "Hello, World!";
        };
        FutureTask<String> future = new FutureTask<>(task);
        new Thread(future).start();
        System.out.println(future.get());
    }
}
例からわかるように、 メソッドを使用して、get問題から結果を取得しますtask(!)重要、メソッドを使用して結果が取得された瞬間に、get実行は同期になります。ここではどのようなメカニズムが使用されると思いますか? そうです、同期ブロックはありません。したがって、JVisualVM のWAITING はmonitorまたはとしてではなくwait、まったく同じものとして表示されますpark(メカニズムが使用されているためLockSupport)。

機能インターフェイス

次に Java 1.8 のクラスについて説明しますので、簡単に紹介しておきます。次のコードを見てみましょう。
Supplier<String> supplier = new Supplier<String>() {
	@Override
	public String get() {
		return "String";
	}
};
Consumer<String> consumer = new Consumer<String>() {
	@Override
	public void accept(String s) {
		System.out.println(s);
	}
};
Function<String, Integer> converter = new Function<String, Integer>() {
	@Override
	public Integer apply(String s) {
		return Integer.valueOf(s);
	}
};
無駄なコードが多いですね。宣言された各クラスは単一の関数を実行しますが、それを記述するために不必要な補助コードを大量に使用します。Java 開発者もそう考えていました。したがって、彼らは一連の「関数インターフェース」 ( @FunctionalInterface) を導入し、重要なものを除いて Java 自体がすべてを「考え出す」ことにしました。
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier- プロバイダー。パラメータはありませんが、何かを返します、つまり、それを提供します。 Consumer- 消費者。何かを入力 (パラメータ) として受け取り、それを使って何かを行います、つまり、何かを消費します。別の機能があります。入力 (パラメータ) として何かを受け取りs、何かを実行して、何かを返します。ご覧のとおり、ジェネリック医薬品が積極的に使用されています。よくわからない場合は、それらを思い出して「Java のジェネリックスの理論または実際に括弧を置く方法」を読んでください。

完成可能な未来

時間が経つにつれて、Java 1.8 では と呼ばれる新しいクラスが導入されましたCompletableFuture。これはインターフェースを実装しますFuture。これは、私たちのインターフェースtaskが将来実行され、実行しgetて結果を取得できることを意味します。しかし、彼はいくつかの も実装していますCompletionStage。翻訳からその目的はすでに明らかです。それはある種の計算の特定の段階です。このトピックの簡単な紹介は、概要「CompletionStage および CompletableFuture の概要」に記載されています。早速本題に入りましょう。開始に役立つ、利用可能な静的メソッドのリストを見てみましょう。 スレッドで Java を台無しにすることはできません: パート IV - Callable、Future とその仲間たち - 2それらを使用するためのオプションは次のとおりです。
import java.util.concurrent.CompletableFuture;
public class App {
    public static void main(String []args) throws Exception {
        // CompletableFuture уже содержащий результат
        CompletableFuture<String> completed;
        completed = CompletableFuture.completedFuture("Просто meaning");
        // CompletableFuture, запускающий (run) новый поток с Runnable, поэтому он Void
        CompletableFuture<Void> voidCompletableFuture;
        voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("run " + Thread.currentThread().getName());
        });
        // CompletableFuture, запускающий новый поток, результат которого возьмём у Supplier
        CompletableFuture<String> supplier;
        supplier = CompletableFuture.supplyAsync(() -> {
            System.out.println("supply " + Thread.currentThread().getName());
            return "Значение";
        });
    }
}
CompletableFutureこのコードを実行すると、作成にはチェーン全体の開始が含まれること がわかります。したがって、Java8 の SteamAPI には類似点もありますが、これがこれらのアプローチの違いです。例えば:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
これは Java 8 Stream API の例です (詳細については、「図と例の Java 8 Stream API ガイド」を参照してください)。このコードを実行してもExecuted表示されません。つまり、Java でストリームを作成する場合、ストリームはすぐには開始されず、値が必要になるまで待機します。ただしCompletableFuture、計算された値を要求されるのを待たずに、すぐに実行のためのチェーンを開始します。これを理解することが重要だと思います。そこで、CompletableFuture が登場しました。どうすれば連鎖を生み出すことができるのか、どんな手段があるのか​​。前に書いた関数インターフェイスについて思い出してください。
  • FunctionA を受け取り B を返す関数 ( ) があります。この関数には 1 つのメソッドapply(apply) があります。
  • A を受け入れて何も返さないコンシューマ ( Consumer) があります ( Void )。方法は 1 つだけです - accept(受け入れる)。
  • Runnable受け入れもリターンも行わないスレッドでコードが実行されています。メソッドは 1 つだけです - run(実行)。
2 番目に覚えておくべきことは、CompletalbeFutureその作業でRunnableコンシューマと関数を使用するということです。このことを考慮すると、CompletableFuture次のことができることを常に覚えておくことができます。
public static void main(String []args) throws Exception {
        AtomicLong longValue = new AtomicLong(0);
        Runnable task = () -> longValue.set(new Date().getTime());
        Function<Long, Date> dateConverter = (longvalue) -> new Date(longvalue);
        Consumer<Date> printer = date -> {
            System.out.println(date);
            System.out.flush();
        };
        // CompletableFuture computation
        CompletableFuture.runAsync(task)
                         .thenApply((v) -> longValue.get())
                         .thenApply(dateConverter)
                         .thenAccept(printer);
}
メソッドにはバージョンがありthenRunます。_ これは、これらのステージが新しいスレッドで実行されることを意味します。特別なプールから採取されるため、新しいか古いか、どのような流れになるかは事前にはわかりません。すべてはタスクの難易度によって決まります。これらの方法に加えて、さらに 3 つの興味深い可能性があります。わかりやすくするために、どこかからメッセージを受信する特定のサービスがあり、それに時間がかかると想像してみましょう。 thenApplythenAcceptAsync
public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
次に、その他の機能を見てみましょうCompletableFuture。結果をCompletableFuture別の結果と組み合わせることができますCompletableFuture
Supplier newsSupplier = () -> NewsService.getMessage();

CompletableFuture<String> reader = CompletableFuture.supplyAsync(newsSupplier);
CompletableFuture.completedFuture("!!")
				 .thenCombine(reader, (a, b) -> b + a)
				 .thenAccept(result -> System.out.println(result))
				 .get();
デフォルトではスレッドはデーモン スレッドになるため、わかりやすくするために をget使用して結果を待つことに注意してください。そして、結合するだけでなく、以下を返すこともできますCompletableFuture
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
ここでは、簡潔にするために、 という方法が使用されたことに注意してくださいCompletableFuture.completedFuture。このメソッドは新しいスレッドを作成しないため、チェーンの残りの部分は呼び出されたスレッドと同じスレッドで実行されますcompletedFuture。という方法もありますthenAcceptBoth。これは と非常に似ていますacceptが、thenAcceptを受け入れる場合consumerは、別の+を入力としてthenAcceptBoth受け入れます。つまり、 は 1 つではなく 2 つのソースを入力として受け入れます。この単語には別の興味深い可能性があります。 これらのメソッドは代替を受け入れ、最初に実行されたメソッドで実行されます。そして、もう 1 つの興味深い機能であるエラー処理についてこのレビューを終えたいと思います。 CompletableStageBiConsumerconsumerEitherスレッドで Java を台無しにすることはできません: パート IV - Callable、Future とその仲間たち - 3CompletableStageCompletableStageCompletableFuture
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
このコードは何も行いません。なぜなら... 例外がスローされ、何も起こりません。ただし、 のコメントを解除するとexceptionally、動作が定義されます。このトピックに関するCompletableFuture次のビデオも見ることをお勧めします 。 私の謙虚な意見では、これらのビデオはインターネット上で最もビジュアルなものの 1 つです。彼らからは、すべてがどのように機能するのか、私たちがどのような武器を持っているのか、そしてなぜそれが必要なのかが明らかになるはずです。

結論

計算後にスレッドを使用して計算を取得する方法が明確になったことを願っています。追加資料: #ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION