CodeGym /Java Blog /ランダム /Java と Thread クラスを組み合わせるとさらに効果的です。パート IV — Callable、Futur...
John Squirrels
レベル 41
San Francisco

Java と Thread クラスを組み合わせるとさらに効果的です。パート IV — Callable、Future、そして友人たち

ランダム グループに公開済み

序章

パート I では、スレッドの作成方法を確認しました。もう一度思い出してみましょう。 Java と Thread クラスを組み合わせるとさらに効果的です。 パート 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 1.5 で登場したjava.util.concurrent.Callableという兄弟があることがわかりました。違いは何ですか? このインターフェイスの Javadoc を詳しく見ると、 とは異なりRunnable、新しいインターフェイスではcall()結果を返すメソッドが宣言されていることがわかります。また、デフォルトでは例外がスローされます。つまり、チェックされた例外をブロックする必要がなくなりますtry-catch。悪くないですよね?これで、次の代わりに新しいタスクができましたRunnable

Callable task = () -> {
	return "Hello, World!";
};
しかし、それをどうすればいいのでしょうか?結果を返すスレッド上でタスクを実行する必要があるのはなぜでしょうか? 当然のことながら、将来実行されるアクションについては、それらのアクションの結果を将来受け取ることが期待されます。そして、対応する名前を持つインターフェイスがあります。java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Futureインターフェースは、将来結果を受け取る予定のタスクを操作するための API (結果を取得するメソッドとステータスを確認するメソッド) を定義しますに関しては、 java.util.concurrent.FutureTaskFutureクラスでの実装に興味があります。で実行される「タスク」です。この実装がさらに興味深いのは、Runnable も実装されていることです。これは、スレッド上でタスクを処理する古いモデルと新しいモデル (Java 1.5 で登場したという意味で新しい) の間の一種のアダプターであると考えることができます。以下に例を示します。 Future

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メソッドを使用してタスクから結果を取得します。 ノート:メソッドを使用して結果を取得すると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);
	}
};
余分なコードがたくさんあると思いませんか? 宣言された各クラスは 1 つの関数を実行しますが、それを定義するために追加のサポート コードを大量に使用します。これが Java 開発者が考えた方法です。したがって、彼らは一連の「関数インターフェース」 ( @FunctionalInterface) を導入し、Java 自体が「思考」を実行し、私たちが考慮すべき重要なことだけを残すことにしました。

Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
備品Supplier。パラメータはありませんが、何かを返します。こうやって物を供給していくのです。AはConsumer消費します。何かを入力 (引数) として受け取り、それに対して何かを実行します。引数はそれが消費するものです。それから、 もありますFunction。入力 (引数) を受け取り、何かを実行して、何かを返します。ジェネリック医薬品を積極的に活用していることがわかります。よくわからない場合は、「 Java のジェネリックス: 実際に山かっこを使用する方法」を読んで復習してください。

完成可能な未来

CompletableFuture時が経ち、 Java 1.8 ではという新しいクラスが登場しました。これはFutureインターフェースを実装します。つまり、タスクは将来完了し、呼び出してget()結果を取得できます。ただし、CompletionStageインターフェイスも実装されています。名前がすべてを物語っています。これは、一連の計算の特定の段階です。このトピックの簡単な紹介は、「CompletionStage および CompletableFuture の概要」のレビューにあります。早速本題に入りましょう。開始に役立つ利用可能な静的メソッドのリストを見てみましょう。 Java と Thread クラスを組み合わせるとさらに効果的です。 パート IV — Callable、Future、そして友人たち - 2それらを使用するためのオプションは次のとおりです。

import java.util.concurrent.CompletableFuture;
public class App {
    public static void main(String[] args) throws Exception {
        // A CompletableFuture that already contains a Result
        CompletableFuture<String> completed;
        completed = CompletableFuture.completedFuture("Just a value");
        // A CompletableFuture that runs a new thread from Runnable. That's why it's Void
        CompletableFuture<Void> voidCompletableFuture;
        voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("run " + Thread.currentThread().getName());
        });
        // A CompletableFuture that starts a new thread whose result we'll get from a Supplier 
        CompletableFuture<String> supplier;
        supplier = CompletableFuture.supplyAsync(() -> {
            System.out.println("supply " + Thread.currentThread().getName());
            return "Value";
        });
    }
}
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 でストリームが作成されても、ストリームはすぐには開始されません。代わりに、誰かがそこに値を求めてくるのを待ちます。ただし、CompletableFuture誰かが値を要求するのを待たずに、すぐにパイプラインの実行を開始します。これを理解することが重要だと思います。それで、私たちは を持っていますCompletableFuture。パイプライン (またはチェーン) はどのように作成でき、どのようなメカニズムがあるのでしょうか? 前に書いた関数インターフェイスを思い出してください。
  • FunctionA を受け取り、B を返す があります。これには 1 つのメソッドがあります: apply()
  • ConsumerA を受け取り、何も返さない (Void) があります。メソッドは 1 つだけですaccept()
  • Runnableスレッド上で実行され、何も受け取らず、何も返しません。メソッドは 1 つだけですrun()
次に覚えておくべきことは、 がその動作で、、CompletableFutureを使用することです。したがって、 を使用して次のことができることを常に知ることができます。 RunnableConsumersFunctionsCompletableFuture

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()thenApply()およびthenAccept()メソッドには「非同期」バージョンがあります。これは、これらのステージが別のスレッドで完了することを意味します。このスレッドは特別なプールから取得されるため、新しいスレッドになるか古いスレッドになるかは事前にわかりません。それはすべて、タスクの計算負荷がどれだけ高いかによって決まります。これらの方法に加えて、さらに 3 つの興味深い可能性があります。わかりやすくするために、どこかから何らかのメッセージを受信する特定のサービスがあると想像してみましょう。これには時間がかかります。

public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
次に、提供される他の能力を見てみましょうCompletableFuture。a の結果を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()結果を待つために を使用することに注意してください。を組み合わせるだけでなくCompletableFutures、以下を返すこともできますCompletableFuture

CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
ここで、この方法は簡潔にするために使用されたことに注意したいと思いますCompletableFuture.completedFuture()。このメソッドは新しいスレッドを作成しないため、パイプラインの残りの部分は呼び出されたのと同じスレッドで実行されますcompletedFuture。という方法もありますthenAcceptBoth()。これは と非常に似ていますaccept()が、thenAccept()a を受け入れる場合Consumer、入力としてthenAcceptBoth()別のCompletableStage+を受け入れます。つまり、 aは 1 つのソースではなく 2 つのソースを受け取ります。名前に「Either」という単語が含まれるメソッドによって提供されるもう 1 つの興味深い機能があります。 これらのメソッドは代替を受け入れ、最初に実行されるメソッドで実行されます。最後に、もう 1 つの興味深い機能であるエラー処理 についてこのレビューを終了したいと思います。BiConsumerconsumerJava と Thread クラスを組み合わせるとさらに効果的です。 パート 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));
例外が発生し、他には何も起こらないため、このコードは何も行いません。ただし、「例外的に」ステートメントのコメントを解除することで、期待される動作を定義します。について言えばCompletableFuture、次のビデオも見ることをお勧めします。 私の謙虚な意見では、これらはインターネット上で最も説明的なビデオの 1 つです。これらすべてがどのように機能するのか、どのようなツールキットが利用できるのか、そしてなぜこれらすべてが必要なのかを明確にする必要があります。

結論

完了後にスレッドを使用して計算を取得する方法が明確になったことを願っています。追加資料: Java と Thread クラスを組み合わせるとさらに効果的です。パート I — 実行スレッド 組み合わせるとさらに効果的: Java と Thread クラス。パート II — 同期 併用するとより効果的です: Java と Thread クラス。パート III — 連携 を強化: Java と Thread クラス。パート V — Executor、ThreadPool、Fork/Join を 組み合わせるとさらに効果的: Java と Thread クラス。パート VI — 撃て!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION