JavaにおけるPromiseパターン:非同期タスクを効率化し、パフォーマンスを向上させる
別名
- Deferred
- Future
Promiseデザインパターンの目的
Promiseデザインパターンは、最初は不明だが将来解決される結果のプレースホルダーを提供することにより、非同期操作を処理するために使用されます。
実際の例を用いたPromiseパターンの詳細な説明
実世界の例
オンラインのピザ注文システムでは、顧客が注文すると、システムはすぐに注文を確認し、追跡番号(Promise)を提供します。ピザの調理と配達プロセスはバックグラウンドで非同期的に行われます。顧客は追跡番号を使用していつでも注文の状況を確認できます。ピザが調理され、配達に出されると、顧客は配達状況に関する通知(Promiseの解決)を受け取ります。材料が不足している、配達が遅れているなどの問題が発生した場合、顧客にはエラー(Promiseの拒否)が通知されます。
この例えは、Promiseデザインパターンが非同期タスクをどのように管理し、初期リクエストと最終的な結果を分離し、結果とエラーの両方を効率的に処理するかを示しています。
簡単な言葉で
Promiseは、進行中の非同期操作のプレースホルダーです。
Wikipediaによると
コンピュータサイエンスでは、Future、Promise、Delay、Deferredは、一部の並行プログラミング言語でプログラムの実行を同期するために使用される構成要素を指します。これらは、通常は値の計算がまだ完了していないため、最初は不明な結果のプロキシとして機能するオブジェクトを表します。
JavaにおけるPromiseパターンのプログラム例
Promiseデザインパターンは、非同期操作を処理するために並行プログラミングでよく使用されるソフトウェアデザインパターンです。Promiseが作成された時点では必ずしもわかっていない値のプロキシを表します。非同期アクションの最終的な成功値または失敗理由にハンドラーを関連付けることができます。
提供された例では、Promiseを使用してファイルをダウンロードし、行数のカウントや文字頻度分析などの操作を非同期的に実行することで、実際アプリケーションでのパターンの有用性を示しています。
@Slf4j
public class App {
private static final String DEFAULT_URL =
"https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md";
private final ExecutorService executor;
private App() {
// Create a thread pool with 2 threads
executor = Executors.newFixedThreadPool(2);
}
public static void main(String[] args) {
var app = new App();
app.promiseUsage();
}
private void promiseUsage() {
calculateLineCount();
calculateLowestFrequencyChar();
}
private void calculateLowestFrequencyChar() {
// Create a promise to calculate the lowest frequency character
lowestFrequencyChar().thenAccept(
charFrequency -> {
LOGGER.info("Char with lowest frequency is: {}", charFrequency);
}
);
}
private void calculateLineCount() {
// Create a promise to calculate the line count
countLines().thenAccept(
count -> {
LOGGER.info("Line count is: {}", count);
}
);
}
private Promise<Character> lowestFrequencyChar() {
// Create a promise to calculate the character frequency and then find the lowest frequency character
return characterFrequency().thenApply(Utility::lowestFrequencyChar);
}
private Promise<Map<Character, Long>> characterFrequency() {
// Create a promise to download a file and then calculate the character frequency
return download(DEFAULT_URL).thenApply(Utility::characterFrequency);
}
private Promise<Integer> countLines() {
// Create a promise to download a file and then count the lines
return download(DEFAULT_URL).thenApply(Utility::countLines);
}
private Promise<String> download(String urlString) {
// Create a promise to download a file
return new Promise<String>()
.fulfillInAsync(
() -> Utility.downloadFile(urlString), executor)
.onError(
throwable -> {
LOGGER.error("An error occurred: ", throwable);
}
);
}
}
このコードでは、`Promise`クラスを使用して、さまざまな操作のPromiseを作成しています。`thenApply`メソッドはPromiseをチェーンするために使用されます。つまり、1つのPromiseの結果が次のPromiseの入力として使用されます。`thenAccept`メソッドは、Promiseの結果を処理するために使用されます。`fulfillInAsync`メソッドは、Promiseを非同期的に実行するために使用され、`onError`メソッドは、Promiseの実行中に発生したエラーを処理するために使用されます。
プログラム出力
08:19:33.036 [pool-1-thread-2] INFO com.iluwatar.promise.Utility -- Downloading contents from url: https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md
08:19:33.036 [pool-1-thread-1] INFO com.iluwatar.promise.Utility -- Downloading contents from url: https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md
08:19:33.419 [pool-1-thread-2] INFO com.iluwatar.promise.Utility -- File downloaded at: /var/folders/sg/9_st37nn5hq_bfhp8hw2dcrw0000gp/T/promise_pattern12403918065536844551.tmp
08:19:33.419 [pool-1-thread-1] INFO com.iluwatar.promise.Utility -- File downloaded at: /var/folders/sg/9_st37nn5hq_bfhp8hw2dcrw0000gp/T/promise_pattern11215446820862558571.tmp
08:19:33.419 [pool-1-thread-1] INFO com.iluwatar.promise.App -- Line count is: 164
08:19:33.426 [pool-1-thread-2] INFO com.iluwatar.promise.App -- Char with lowest frequency is: ’
JavaでPromiseパターンを使用する場面
- 非同期タスクを実行し、その結果またはエラーを後で処理する必要がある場合。
- タスクを並列に実行でき、完了したらその結果を処理する必要がある場合。
- 非同期コードの可読性と保守性を向上させるのに適しています。
Promiseパターン Javaチュートリアル
- Java 8のCompletableFutureを使用した関数型コールバック(InfoQ)
- CompletableFutureガイド(Baeldung)
- あなたはPromiseのポイントを見逃しています(Domenic Denicola)
JavaにおけるPromiseパターンの実際のアプリケーション
- JavaのCompletableFutureクラスとFutureクラス。
- 非同期操作を管理するためのJavaScriptのPromiseオブジェクト。
- RxJavaやVert.xなどの多くの非同期フレームワークと言語。
- Guava ListenableFuture
Promiseパターンの利点とトレードオフ
利点
- 可読性の向上:複雑な非同期コードを簡素化し、理解と保守を容易にします。
- 分離:非同期操作を開始するコードと結果を処理するコードを分離します。
- エラー処理:非同期操作の結果とエラーの両方を処理するための統一された方法を提供します。
トレードオフ
- 複雑さ:過剰に使用または誤用すると、コードベースに複雑さが増す可能性があります。
- デバッグ:非同期コードは、実行フローが非線形であるため、同期コードに比べてデバッグが難しい場合があります。
関連するJavaデザインパターン
- オブザーバー:Promiseは、オブザーバーパターンと組み合わせて使用して、非同期操作の完了をサブスクライバーに通知できます。
- コールバック:Promiseは、非同期結果を処理するためのより構造化され、読みやすい方法を提供することにより、コールバックメカニズムを置き換えることがよくあります。
- 非同期メソッド呼び出し:Promiseは、非同期メソッド呼び出しの結果を処理するためによく使用され、非ブロッキング実行と結果処理を可能にします。