Javaの非同期処理を基礎から解説!初心者でもわかるThreadクラスの使い方
新人
「先輩、Javaの非同期処理ってなんですか?なんとなく難しそうで…」
先輩
「非同期処理は、処理が終わるのを待たずに次の処理に進める技術のことだよ。JavaではThreadクラスやExecutorServiceを使って実装するのが基本なんだ。」
新人
「処理を待たないって、どういう時に使うんですか?」
先輩
「例えばWebアプリでファイルをアップロードする時、完了を待たずに次の操作ができるようにする場合だね。じゃあ、まずはJavaにおける非同期処理の基本から見ていこうか。」
1. 非同期処理とは?Javaでの基本的な考え方
Javaの非同期処理とは、処理の完了を待たずに別の処理を並行して行う仕組みです。通常、Javaのプログラムは上から下に順番に処理されます(同期処理)。しかし、時間がかかる処理があると、その間プログラム全体が止まってしまいます。
このような場面で、非同期処理を活用すると、処理をバックグラウンドで実行し、メインの処理は止まることなく先に進むことができます。Javaではこの非同期処理を、ThreadクラスやExecutorServiceなどで実装できます。
特にWebアプリケーションや並列処理の必要なアプリでは、非同期処理はパフォーマンス向上のために非常に重要な技術です。
2. Threadクラスを使った非同期処理の基本
Javaでは、非同期処理を実装する方法として、Threadクラスを直接使う方法があります。以下はその基本的な使い方の例です。
まずは、Threadクラスを継承したクラスを作成し、その中に非同期で動かしたい処理を書きます。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("非同期処理が実行されています。");
}
public static void main(String[] args) {
System.out.println("メイン処理を開始します。");
MyThread thread = new MyThread();
thread.start(); // 非同期でrun()が実行される
System.out.println("メイン処理を継続します。");
}
}
このプログラムを実行すると、run()メソッドの中の処理が非同期で実行され、メインスレッドはそのまま次の処理に進みます。実行結果の例は以下のようになります。
メイン処理を開始します。
メイン処理を継続します。
非同期処理が実行されています。
このように、Threadクラスを使うことで、初心者でも簡単にJavaで非同期処理を体験できます。ただし、非同期の順序は実行環境によって変わることがあるため、結果の順番が毎回同じとは限りません。
さらに、Threadを使う方法にはもう一つのやり方があり、Runnableインターフェースを使って処理内容を定義する方法です。
public class RunnableExample {
public static void main(String[] args) {
System.out.println("メイン処理開始");
Runnable task = () -> {
System.out.println("Runnableを使った非同期処理");
};
Thread thread = new Thread(task);
thread.start();
System.out.println("メイン処理継続");
}
}
Runnableを使うと、Javaのラムダ式(Java8以降)との相性が良く、より柔軟でスッキリとしたコードになります。
このように、JavaのThreadクラスやRunnableを使うことで、初心者でもシンプルに非同期処理を実装することができます。次回は、複数のスレッドを効率的に管理できるExecutorServiceについて解説していきます。
3. Runnableインターフェースの使い方とThreadクラスとの違い
Javaで非同期処理を実装する場合、Threadクラスを継承する方法の他に、Runnableインターフェースを使う方法があります。Runnableは、run()メソッドだけを実装するシンプルなインターフェースで、Java初心者にとっても理解しやすい構造です。
Threadを継承する場合、そのクラスは他のクラスを継承できなくなる制限がありますが、Runnableを使えばこの制約を回避できます。Javaではクラスの継承は1つしかできないため、Runnableの利用は柔軟性を高める選択となります。
以下にRunnableを使った基本的な非同期処理の例を示します。
public class RunnableTask implements Runnable {
@Override
public void run() {
System.out.println("Runnableインターフェースで非同期処理を実行しています。");
}
public static void main(String[] args) {
System.out.println("メイン処理開始");
Thread thread = new Thread(new RunnableTask());
thread.start();
System.out.println("メイン処理継続");
}
}
Runnableを使うことで、非同期処理の定義とスレッドの管理を分離できます。これにより、処理内容とスレッドの生成を明確に分けられるため、より読みやすく保守性の高いコードになります。
4. ExecutorServiceとは?非同期処理を効率よく管理する方法
Javaで複数のスレッドを効率よく管理したい場合は、ExecutorServiceの利用が推奨されます。ExecutorServiceはJava標準ライブラリに含まれるインターフェースで、スレッドの生成やスケジューリング、終了処理を一括して管理できます。
初心者がThreadクラスだけで複雑な処理を管理しようとすると、スレッドの数が増えたときに制御が難しくなります。そこで登場するのがExecutorServiceです。これを使うことで、スレッドの再利用や実行の順序、終了の確認などが簡単に行えるようになります。
例えば、以下のようにExecutors.newSingleThreadExecutor()を使えば、1つのスレッドで順番に処理を実行できます。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
System.out.println("ExecutorServiceで非同期処理");
});
executor.shutdown(); // 処理完了後にシャットダウン
}
}
ExecutorServiceには複数の種類があり、newFixedThreadPoolやnewCachedThreadPoolなど用途に応じて使い分けることができます。こうしたスレッドプールの仕組みを使うと、毎回新しいスレッドを作成する必要がなくなり、システムリソースの節約にもなります。
GradleでSpringを使った開発でも、このExecutorServiceを利用することで、非同期処理を安全かつ効率的に構築できます。
5. submitとexecuteの違いを理解しよう
ExecutorServiceには、非同期タスクを実行するための2つの代表的なメソッドがあります。それがexecute()とsubmit()です。初心者が混乱しやすいポイントでもあるので、それぞれの違いを明確に理解しておきましょう。
execute()は、Runnableを引数に取り、結果を返さない非同期処理に適しています。簡単なログ出力や、完了確認が不要な処理に向いています。
一方、submit()は、RunnableまたはCallableを引数に取り、Futureオブジェクトを返します。これにより、後から非同期処理の結果を取得したり、エラーを捕捉したり、完了を待機したりすることができます。
以下はsubmit()の例です。
import java.util.concurrent.*;
public class SubmitExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
return "非同期処理の結果";
});
String result = future.get(); // 処理完了まで待機
System.out.println(result);
executor.shutdown();
}
}
submit()を使えば、非同期処理の結果を後から取得することができます。get()メソッドは処理が終わるまで待機するため、同期的な結果取得となりますが、必要に応じて時間制限を設けたり、キャンセルすることも可能です。
このように、JavaにおけるExecutorServiceのexecute()とsubmit()は、それぞれ用途が異なります。初心者の方は、「結果が不要ならexecute、必要ならsubmit」と覚えておくとよいでしょう。
6. 非同期処理における注意点(スレッド数と例外処理)
Javaで非同期処理を実装する際には、便利さだけに注目するのではなく、正しく扱わなければならない注意点もあります。特に初心者の方が見落としがちなのが「スレッド数の管理」と「例外処理」です。
まずスレッド数の管理についてですが、new Thread()を何度も呼び出してスレッドを無制限に作ると、システムのリソースを大量に消費してしまい、アプリケーションが不安定になります。これを防ぐために、ExecutorServiceを使い、あらかじめ最大スレッド数を制限したFixedThreadPoolを利用するのが一般的です。
また、非同期処理の中で発生した例外は、メインスレッドに伝わりません。これが原因でエラーに気づかず、アプリケーションが意図せず止まってしまうこともあります。
例えば以下のコードでは、非同期処理中に例外が発生しても、プログラム全体は止まりませんが、ログなどに出力されないと原因が分かりづらくなります。
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
throw new RuntimeException("例外が発生しました!");
});
このような例外を確実にキャッチしたい場合は、submit()を使ってFutureで包み、get()メソッドでチェックする方法があります。非同期処理ではエラーの取り扱いが難しくなるため、ログ出力やモニタリングの仕組みも合わせて導入するとよいでしょう。
7. shutdownの重要性とその使い方
ExecutorServiceを使用した非同期処理では、最後にshutdown()を必ず呼ぶことが大切です。これを忘れると、Javaプログラムが終了せず、バックグラウンドでスレッドが残り続けることになります。
初心者が非同期処理を学ぶ中で、ありがちなミスがこのshutdown()の呼び忘れです。特にSpringを使っていない通常のJavaアプリケーションでは、スレッドプールを明示的に閉じる必要があります。
以下は、正しくshutdown()を呼び出している例です。
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> {
System.out.println("非同期処理を実行中...");
});
// 処理の受付を終了(実行中のものはそのまま継続)
executor.shutdown();
また、shutdownNow()というメソッドもありますが、こちらは現在実行中の処理も強制終了するため、基本的には推奨されません。安全に処理を終えるには、通常のshutdown()を使用し、必要に応じてawaitTermination()で待機する設計が望ましいです。
executor.shutdown();
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("まだ処理中です。");
}
このように、非同期処理では、スレッドの終了処理も含めたライフサイクル全体を意識することが重要です。特にJava初心者の方は、ExecutorServiceを使うときには、最後にshutdown()を忘れないようにしましょう。
8. Java初心者が非同期処理を学ぶためのコツと学習方法
Javaの非同期処理は一見難しそうに感じられるかもしれませんが、少しずつ仕組みを理解すれば、初心者でも確実に習得できます。以下では、非同期処理を学ぶ際のポイントとおすすめの学習方法を紹介します。
まず、非同期処理の基礎としてThreadとRunnableを実際に自分で書いてみることが大切です。コードの流れや処理の順番を目で確認することで、「同期処理」と「非同期処理」の違いが明確に見えてきます。
次に、ExecutorServiceを使ったサンプルコードを繰り返し実行しながら、スレッドプールの動きやsubmit()とexecute()の違い、shutdown()のタイミングなどを体感しましょう。
具体的な学習ステップとしては、以下の流れがおすすめです。
- 1.
Threadを使った簡単な非同期処理を実装 - 2.
Runnableインターフェースの書き方を練習 - 3.
ExecutorServiceでのスレッド管理を体験 - 4.
submit()とFutureの組み合わせで結果取得 - 5. 例外処理やスレッドの終了方法(
shutdown())を習得
さらに、Springを使ったJavaアプリでも非同期処理は重要な役割を果たします。Springの@Asyncを使った方法などは、基礎を理解した後にステップアップとして学ぶと良いでしょう。
また、Pleiades環境での開発であれば、Gradle構成での依存関係追加も簡単に行えるため、実践的な環境で非同期処理の学習が進められます。
繰り返しになりますが、Javaの非同期処理をマスターするには、「小さなコードを書いて動かす→挙動を確認する→少しずつ応用を試す」というステップを繰り返すことが一番の近道です。焦らずに、じっくり学んでいきましょう。