Javaで非同期処理をしよう!初心者からの完全ガイド8選

Javaでの非同期処理を学ぶための完全ガイドのサムネイル画像Java
この記事は約22分で読めます。

※本記事のコンテンツは、利用目的を問わずご活用いただけます。実務経験10000時間以上のエンジニアが監修しており、常に解説内容のわかりやすさや記事の品質に注力しておりますので、不具合・分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。(理解できない部分などの個別相談も無償で承っております)
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

非同期処理は現代のプログラミングにおいて非常に重要なテーマであり、多くのプログラミング言語が非同期処理をサポートしています。

Javaもその一つであり、非同期処理を利用することで、パフォーマンスの向上やリソースの効率的な利用が可能となります。

この記事では、Javaでの非同期処理の基本からその必要性、さらには詳細な使い方までを徹底的に解説します。

●Javaと非同期処理の基本

Javaはオブジェクト指向プログラミング言語として広く知られ、非同期処理の実装も可能です。

非同期処理は、特定のタスクが完了するのを待つことなく、他のタスクを同時に進行させることができるプログラミングの技法です。

○非同期処理とは?

非同期処理は、一言で言うと、「メインの作業フローを停止させることなく、別の作業を同時に進行させる方法」です。

この技法は、特定のタスクが時間がかかる場合や、外部リソースにアクセスする場合など、効率的にリソースを利用する必要があるシチュエーションで特に役立ちます。

例えば、ウェブページをロードするプロセスを考えてみましょう。

ウェブページには多くの要素(画像、テキスト、ビデオなど)がありますが、これらの要素を一つ一つシーケンシャルにロードするのではなく、非同期的に複数の要素を同時にロードすることで、ウェブページのロード時間を大幅に短縮できます。

○Javaでの非同期処理の必要性

Javaにおける非同期処理の必要性は、大規模なアプリケーションやサービスの開発において特に顕著です。

非同期処理を利用することで、アプリケーションのレスポンス時間の短縮やリソースの効率的な利用が可能となります。

また、Javaでは、多くのライブラリやフレームワークが非同期処理をサポートしており、これによって開発者は非同期処理を簡単かつ効果的に実装できます。

非同期処理をうまく利用することで、アプリケーションのスケーラビリティを向上させることができます。

●Java非同期処理の詳細な使い方

Javaにおける非同期処理は、特定のタスクをメインスレッドから別のスレッドで実行させることを意味します。

この方法は、特に時間のかかるタスクやブロッキングオペレーションを別のスレッドで行うことで、メインスレッドをブロックすることなく、アプリケーションのレスポンスタイムを短縮するのに役立ちます。

○サンプルコード1:基本的な非同期処理の書き方

Javaで非同期処理を実装する最も基本的な方法は、Threadクラスを利用することです。

非同期処理を行う基本的な例を紹介します。

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        // 非同期で実行する処理
        System.out.println("非同期での処理を開始します");
        try {
            Thread.sleep(2000); // 2秒間スリープ
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("非同期での処理が完了しました");
    }
});
thread.start();

このサンプルコードでは、Threadクラスのインスタンスを作成しています。

Runnableインターフェースのrunメソッド内に非同期で実行したい処理を記述し、thread.start()で非同期処理を開始します。

具体的には、メッセージを表示した後、2秒間スリープしてから再度メッセージを表示します。

このコードを実行すると、”非同期での処理を開始します”というメッセージが表示され、2秒後に”非同期での処理が完了しました”というメッセージが表示されることが期待されます。

○サンプルコード2:FutureとCallableを使用した例

Javaにおける非同期処理の実装方法の一つとして、「Future」と「Callable」インターフェイスを利用した方法があります。

これらを利用することで、非同期処理を行うタスクを効果的に管理し、複数のタスクを同時に行うことが可能となります。

まず最初に、「Callable」インターフェイスについて説明します。

Callableインターフェイスは、Runnableインターフェイスと似たような動作を行いますが、Callableは結果を返すことが可能であり、例外をスローすることもできます。

Callableインターフェイスを利用した非同期処理の基本的なコードは次のようになります。

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // ここで非同期に行いたい処理を記述します。
        Thread.sleep(1000);
        return "非同期処理の結果";
    }
}

上記のコードでは、MyCallableクラスがCallableインターフェイスを実装しています。

callメソッド内で非同期に行う処理を記述し、その処理の結果を返却しています。

この例では、Thread.sleepメソッドを使用して1秒間のスリープを行った後、文字列を返却しています。

次に、「Future」インターフェイスについて説明します。

Futureインターフェイスは、非同期処理の結果を保持するためのインターフェイスです。

非同期処理が完了した際の結果を取得するために使用します。

下記のサンプルコードでは、ExecutorServiceを利用して非同期処理を行い、その結果をFutureインターフェイスを通じて取得しています。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureAndCallableExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<String> future = executorService.submit(new MyCallable());

        try {
            String result = future.get();
            System.out.println("非同期処理の結果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

上記のサンプルコードでは、ExecutorServiceのsubmitメソッドにCallableの実装クラスのインスタンスを渡して非同期処理を実行しています。

そして、Futureのgetメソッドを使用して非同期処理の結果を取得しています。

このコードを実行すると、コンソールに「非同期処理の結果: 非同期処理の結果」と表示されます。

これによって非同期処理の結果を取得することができました。

このように、「Future」と「Callable」を使用した非同期処理の方法は、Javaプログラミングにおいて非常に有用な手法となります。

実際の開発においても、これらの技術を利用することで効率的なプログラムを作成することが可能となります。

また、非同期処理の知識は、多くの高度なプログラムの開発においても利用されるため、基本的な知識として理解しておくことは非常に重要です。

○サンプルコード3:CompletableFutureを利用した非同期処理

Javaの非同期処理の領域でCompletableFutureは非常に強力なクラスです。

この部分では、CompletableFutureを利用した非同期処理に関するサンプルコードとその詳細な解説を行いましょう。

Javaでは非同期処理を行う際に、CompletableFutureクラスを使用することで、煩雑なスレッド管理や例外処理を効率的に行うことができます。

ここでは、その使用方法と実行結果に関して詳細に解説します。

最初に、CompletableFutureを利用した基本的な非同期処理のサンプルコードを紹介します。

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("非同期処理が完了しました");
        });

        System.out.println("非同期処理を開始しました");
        future.join();
        System.out.println("全ての処理が終了しました");
    }
}

上記のサンプルコードに関して、その内容と実行結果を説明します。

このコードは、非同期タスクを実行する簡単なプログラムです。

CompletableFuture.runAsyncメソッドを使用して新しい非同期タスクを作成し、それを実行します。

この非同期タスクは、2秒間スレッドをスリープさせた後、”非同期処理が完了しました”というメッセージをコンソールに出力します。

一方、メインスレッドでは”非同期処理を開始しました”というメッセージを出力した後、future.join()メソッドを呼び出して非同期タスクの完了を待ちます。

非同期タスクが完了すると、”全ての処理が終了しました”というメッセージがコンソールに表示されます。

実行結果は次のようになります。

非同期処理を開始しました
非同期処理が完了しました
全ての処理が終了しました

このサンプルコードとその解説を通じて、JavaのCompletableFutureクラスを使った非同期処理の基本的な利用方法とその実行結果についてご理解いただけたことと思います。

このような非同期処理の技法は、大規模なアプリケーション開発や高負荷な環境でのプログラムの性能を向上させるために非常に重要となります。

●応用例とそのサンプルコード

Javaでの非同期処理は、初心者から上級者まで多くのプログラマーに利用されている技術です。

ここでは、実際に利用される応用例とそれに関連するサンプルコードを超絶詳細に解説いたします。

○サンプルコード4:非同期処理でのデータベースアクセス

データベースアクセスは、時として時間がかかる処理であり、これを非同期で行うことで、その他の処理と並行して行うことができ、効率的なプログラムを実現できます。

下記のサンプルコードは、Javaを使用した非同期処理でのデータベースアクセスの基本的な方法を示しています。

このコードでは、非同期でデータベースにアクセスし、結果を取得しています。

このコードを実行すると、非同期にデータベースへのクエリが実行された結果、データが取得されます。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.concurrent.CompletableFuture;

public class AsyncDatabaseAccess {
    public static void main(String[] args) {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try (Connection connection = DriverManager.getConnection("jdbc:yourdatabaseurl", "yourdatabaseusername", "yourdatabasepassword");
                 Statement statement = connection.createStatement()) {

                ResultSet resultSet = statement.executeQuery("SELECT * FROM yourtable");
                while (resultSet.next()) {
                    System.out.println("Name: " + resultSet.getString("name"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        System.out.println("非同期処理を開始しました");
        future.join();
        System.out.println("非同期処理が終了しました");
    }
}

ここでは、CompletableFuture.runAsyncを利用して非同期にデータベースアクセスの処理を行っています。

非同期処理を開始したことを表すメッセージが最初に表示され、その後データベースからデータを取得する処理が行われます。

非同期処理が終了したことを表すメッセージが最後に表示されます。

このコードは、非同期処理の基本的な使い方を表しており、非同期でデータベースアクセスを行う際の参考にしていただくことができます。

さらに、このコードを実行するとデータベースからデータを取得できることが確認できます。

○サンプルコード5:REST APIの非同期呼び出し

JavaでREST APIを非同期で呼び出す場合、HttpClientを使って非同期のリクエストを行うことができます。

この記事では、まず基本的な非同期のリクエストの作成方法から、その後に非同期リクエストの実行結果を取得し解析する方法までを順を追って詳細に解説します。

最後には実行結果に基づく具体的な処理の方法を説明します。

まず初めにHttpClientを作成し、非同期でAPIリクエストを行う方法を解説します。

JavaでHttpClientを利用する際には、次のようなコードが使用されます。

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(new URI("https://api.example.com/data"))
        .build();

このコードでは、HttpClientインスタンスを新しく作成し、HttpRequestインスタンスを用いてURIを指定してリクエストを作成しています。

この段階ではまだAPIの呼び出しは行われていません。

次に非同期でリクエストを実行する部分に移ります。

次のようなコードを追加します。

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println)
    .join();

ここでは、sendAsyncメソッドを使用して非同期リクエストを行い、その後にレスポンスのボディを取得し、コンソールに出力しています。

thenApplyメソッドでレスポンスボディを取得し、thenAcceptメソッドで取得したボディを出力しています。

最後のjoinメソッドは、非同期処理が終了するまで待つ役割があります。

次に実行結果ですが、このコードを実行すると、コンソールにAPIから取得したレスポンスボディが出力されます。

このレスポンスボディにはAPIから返されたデータが含まれており、これを基にさまざまな処理を行うことができます。

たとえばJSON形式のデータを解析して、特定の情報を取り出す処理を追加することが可能です。

○サンプルコード6:非同期処理のエラーハンドリング

Javaにおける非同期処理のエラーハンドリングは、プログラムの安定性とデバッグ効率の向上に貢献します。

ここでは非同期処理のエラーハンドリングに関する詳細な解説と、実用的なサンプルコードを提供いたします。

コードの説明を行う際には、そのコードの実行結果も交えて説明いたします。

また、非同期処理でのエラーハンドリングの実施方法と、その重要性について丁寧に説明いたします。

Javaでは、CompletableFutureクラスを使用して非同期処理を行います。

このクラスは、非同期処理の結果を保持し、エラーハンドリングも提供します。

exceptionallyメソッドを使用して、エラーが発生した場合のハンドラを指定できます。

CompletableFutureを用いた非同期処理のエラーハンドリングの一例を紹介します。

このサンプルコードでは、非同期処理で発生する可能性のあるエラーを捕捉し、適切にハンドリングします。

import java.util.concurrent.CompletableFuture;

public class AsyncErrorHandling {
    public static void main(String[] args) {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            // 非同期タスクの実行
            throw new IllegalArgumentException("非同期タスクでの例外");
        }).exceptionally(ex -> {
            // エラーハンドリング
            System.out.println("エラーが発生しました: " + ex.getMessage());
            return null;
        });

        // 非同期タスクの完了を待つ
        future.join();
    }
}

このコードの解説を行います。

最初にCompletableFutureクラスを利用して非同期タスクを作成します。

このタスクは、故意にIllegalArgumentExceptionを発生させます。

exceptionallyメソッドを用いてエラーハンドラを設定しています。

エラーが発生した場合、このハンドラが呼び出され、エラーメッセージがコンソールに表示されます。

このコードを実行すると、次のような出力が得られます。

エラーが発生しました: java.lang.IllegalArgumentException: 非同期タスクでの例外

この出力から、非同期タスクで発生したエラーが適切に捕捉され、ハンドラによって処理されたことが確認できます。

このようにCompletableFutureを使うと、非同期処理のエラーハンドリングが簡単に実現できます。

●注意点と詳細な対処法

ここでは、Javaで非同期処理を実施する際の注意点とその詳細な対処法について、一歩一歩丁寧に解説していきます。

非同期処理は、効率的なプログラムの運行を可能にしますが、注意しなければならない要点も多々あります。

そこで、特に重要な注意点を2つ取り上げ、それぞれ詳細な対処法とサンプルコードを用いて説明します。

○スレッドの過剰な使用とその対策

Javaで非同期処理を行うとき、スレッドの過剰な使用は避けなければなりません。

スレッドの過剰な使用はシステムリソースを過度に消費し、パフォーマンスの低下を招く可能性があります。

したがって、この部分ではスレッドの適切な管理と利用に関する対策を詳細に説明します。

ExecutorService executor = Executors.newFixedThreadPool(10); // スレッドプールの作成
executor.submit(() -> {
    // ここに非同期で実行したいタスクを書く
    System.out.println("タスクを実行中");
});
executor.shutdown(); // スレッドプールのシャットダウン

このサンプルコードでは、ExecutorsクラスのnewFixedThreadPoolメソッドを使用して固定サイズのスレッドプールを作成しています。

そして、submitメソッドを使ってタスクを非同期で実行しています。

最後に、shutdownメソッドを呼び出してスレッドプールをシャットダウンしています。

次に、このコードの実行結果について説明します。

コードが正常に実行されると、コンソールに「タスクを実行中」というメッセージが表示されます。

○例外処理のベストプラクティス

非同期処理を行う際には、例外処理も非常に重要な部分となります。

この部分では、非同期処理における例外処理のベストプラクティスを詳しく解説します。

executor.submit(() -> {
    try {
        // ここに非同期で実行したいタスクを書く
        System.out.println("タスクを実行中");
    } catch (Exception e) {
        // 例外処理
        e.printStackTrace();
    }
});

このサンプルコードでは、lambda式を使用して非同期タスクを実行しています。

この非同期タスク内で例外が発生した場合には、catchブロックで捕捉して例外処理を行います。

例外処理の部分では、e.printStackTraceメソッドを使用して例外のスタックトレースを表示します。

●非同期処理のカスタマイズ方法

Javaでの非同期処理を行う際に、様々なカスタマイズ方法があります。

次の見出しで説明する各項目では、具体的なサンプルコードとともに、そのカスタマイズ方法を詳しく解説します。

○サンプルコード7:非同期タスクのタイムアウト設定

Javaにおける非同期タスクのタイムアウト設定は非常に重要です。

下記のコードは、非同期タスクが一定時間内に完了しない場合にタイムアウトを引き起こす一例です。

このサンプルコードでは、Futureオブジェクトのgetメソッドを利用して、指定した時間内にタスクが完了しない場合にTimeoutExceptionをスローします。

import java.util.concurrent.*;

public class AsyncTimeoutExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<String> future = executor.submit(() -> {
            Thread.sleep(5000); // 5秒間スリープ
            return "タスク完了";
        });

        try {
            System.out.println(future.get(3, TimeUnit.SECONDS)); // 3秒でタイムアウト
        } catch (TimeoutException e) {
            System.out.println("タイムアウトが発生しました。");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

このコードを実行すると、”タイムアウトが発生しました。”と表示される結果になります。

タスクが3秒でタイムアウトし、TimeoutExceptionがスローされるためです。

○サンプルコード8:非同期処理の結果をまとめる方法

非同期処理の結果をまとめる方法も、非常に有用なテクニックの一つです。

下記のコードは、CompletableFutureを利用して複数の非同期タスクの結果をまとめる方法を表しています。

import java.util.concurrent.CompletableFuture;

public class AsyncResultAggregationExample {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            return "タスク1";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            return "タスク2";
        });

        CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);

        combinedFuture.thenAccept(v -> {
            try {
                System.out.println("結果: " + future1.get() + ", " + future2.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

このコードを実行すると、”結果: タスク1, タスク2″と表示されます。

CompletableFuture.allOfメソッドを使用して、複数の非同期タスクを同時に実行し、そのすべてのタスクが完了した後に結果を取得しています。

まとめ

Javaで非同期処理を行う際には、初心者から上級者までさまざまな段階の技術や知識が必要です。

今回のガイドでは、基本的な非同期処理の概念から、具体的なサンプルコードを通じて非同期処理の技術を習得できる内容を詳細に説明してまいりました。

このガイドが、Javaで非同期処理を学ぶすべての方々にとって有益な情報源となれば幸いです。

また、本ガイドを通じて非同期処理の技術をさらに深化させ、Javaプログラミングの技術を向上させる手助けとなることを期待しています。

継続的な学習と実践を通じて、皆さんがJavaの非同期処理の達人となれることを祈念しております。