Java のプログラミングを進めていくと「マルチスレッド処理」や「並列処理」という言葉を耳にすることもあるでしょう。
複数の処理を同時に実行され、それらを短期間で処理することが求められるシステムでは、効率的に処理を行うマルチスレッドが求められ、また、不特定多数のユーザーからアクセスされるような Web アプリケーションでは、そもそもマルチスレッド処理は欠かせない技術です。
この記事では、Javaのスレッドを使って、複数の処理を同時に行う方法を解説してきます。
スレッドとは
「スレッド」は Java で処理を行う最小単位で、最低でもメインスレッドと呼ばれる1つのスレッドが作成され、そのスレッド上で Java の処理は実行されます。
1つのスレッドで実行できる命令は1であるため、例えば GUI でネットワーク通信を行うような処理の場合、通信が完了するまで次の命令が実行できないため、GUI が固まった状態の、いわゆるフリーズ状態になります。
そこで Java には Thread クラスや、その他マルチスレッド関連のクラスが用意されており、ネットワーク通信などの時間がかかる処理については、別で作成したスレッドで行わせ、メインスレッドの処理を止めないことで、GUI のフリーズ状態が回避でき応答性の良いアプリケーションを作れます。
また、Web アプリケーションにおいては、ユーザーからアクセス毎にスレッドを起動することで、同時に複数のリクエストを効率的に処理します。
逆に1つのスレッドだけで Webアプリケーションを処理していた場合、1つのリクエストを処理するのに3秒かかり、それが100人のユーザーから同時にアクセスされた場合、最後にリクエストを送信した人の処理が完了するのが 300秒後となり、とめも応答性がよいシステムとは呼べないようでしょう。
マルチスレッドを実装してみよう
マルチスレッドを行う実装として、もっとも基本的なのが Thread クラス継承したクラスを作成しそれを実行する方法です。継承したクラスにスレッド内で処理を行いたい命令を記述し、メインスレッドもしくは親となるスレッドから、 Thread クラス継承したクラスのインスタンスを作成し実行します。
実際に作ってみましょう。
まず、Thread クラス継承したクラスを作成します。スレッド内で行う命令は オーバーライドしたrunメソッドに記述します。
class SampleThread extends Thread {
public void run() {
System.out.println("スレッド開始");
try {
Thread.sleep(3000); //3秒処理を止める
} catch (InterruptedException e) {}
System.out.println("スレッド終了");
}
}
次にメインスレッドから、上で作成したクラスのインスタンスを作成し、スレッドを起動する処理を記述します。
class Main {
public static void main(String[] args) {
SampleThread t = new SampleThread();
t.start();
System.out.println("SampleThreadスレッドを起動");
}
}
これだけで、複数のスレッドを起動する処理の完成です。サンプルコードを実行して結果を確認してみましょう。
実行結果
-----------------------------
SampleThreadスレッドを起動
スレッド開始
スレッド終了
このように、main 関数では、SampleThread クラスのスレッドの起動後、即座に次の命令が実行され、メインスレッドの処理がブロックされていないことが分かります。
処理の待ち合わせ
複数のスレッドを立ち上げると、それぞれスレッドは独立して命令を上から順に実行していきます。しかし時には○○の処理が終わるまでは、□□の処理は実行できないといった制約もあるでしょう。
複数のスレッド間で、このような処理の待ち合わせを行う場合は、wait、notify および notifyAll メソッドを使用することで、待ち合わせ処理を実装することができます。
wait メソッドは、他のスレッドから notify または notifyAll メソッドが呼び出されるまで、スレッドを一時停止するメソッドで、notify および notifyAll メソッドは、wait で一時停止させたレッドを再開させるためのメソッドです。
具体的なサンプルコードを見てみましょう。
class SampleThread extends Thread {
public synchronized void run() {
System.out.println("スレッド開始");
try {
wait(); // waitメソッドを呼び出して、notifyが呼ばれるまでスレッドを停止
} catch (InterruptedException e) {}
System.out.println("スレッド終了");
}
}
class Main {
public static void main(String[] args) {
SampleThread t = new SampleThread();
t.start();
System.out.println("SampleThreadスレッドを起動");
//3秒処理を止める
try {
Thread.sleep(3000);
} catch (InterruptedException e) {}
synchronized(t) {
t.notify();
}
System.out.println("notify()呼び出し");
}
}
上のコードを実行した結果は次のとおりです。t.notify()が呼ばれるまでは、スレッドのwait()メソッドのところで処理が停止しているのが分かります。
実行結果
-------------------------
SampleThreadスレッドを起動
スレッド開始
notify()呼び出し
スレッド終了
これらのメソッドの詳細は、次の記事でも詳しく解説しています。
【関連記事】
▶【Java】wait・notifyを使ってスレッド間の待ち合わせを実装する
スレッドから戻り値を取得する
Thread クラスの run メソッドは void であり戻り値が返せません。スレッドから戻り値を取得する場合は、Callableクラスと Future インターフェイスを使用します。
まず Callable クラスを継承したクラスを作成します。String 型の値を返す Callableクラスであれば次のような形になります。
public class SampleCallable implements Callable<String> {
//callメソッドをオーバーライドしてスレッドの処理を書く
public String call(){
return "Return Callable Value";
}
}
Callable クラスのスレッドの呼び出しは、ExecutorServiceクラスを用いて行い、submit メソッドに実行するスレッドのインスタンスを指定します。
class Main {
public static void main(String[] args) {
ExecutorService ex = Executors.newSingleThreadExecutor();
//SampleCallableを実行
Future<String> futureResult = ex.submit(new SampleCallable());
try {
// Future型からスレッドの戻り値を取り出す
String result = futureResult.get();
System.out.println(result);
}catch (Exception e){
System.out.println(e);
}
}
}
まとめ
Java でスレッドを使用する方法を解説してきました。マルチスレッドプログラミングは、プログラミング学習でつまずきやすいポイントでもあるため、しっかりと理解しておきましょう。
【関連記事】
▶volatile修飾子でJavaのスレッド変数を制御する方法
スレッド処理の結果は、Future インターフェースで返ってきます。スレッドの処理が完了すると、Future#getメソッドでスレッドの戻り値が取得できます。