\r\n\r\n
マルチスレッドは、タスクを並列に実行するコードを書く方法です。Java 1.0の初期から、Javaはマルチスレッドコードを書くための優れたサポートを提供してきました。最近のJavaの機能強化により、Javaプログラムにマルチスレッドコードをビルドする方法が追加されました。
この記事では、次のJavaプロジェクトでどのオプションを使用するかをより良く判断できるように、これらのオプションのいくつかを比較します。
Javaにはスレッドクラスがあり、これを拡張してrun()メソッドを実装することができます。このrun()メソッドにタスクが実装されています。自分のスレッドでタスクを開始したい場合は、このクラスのインスタンスを作成し、そのstart()メソッドを呼び出すとよい。これにより、スレッドの実行が開始され、完了まで実行されます(または例外で終了します)。
以下は、指定した時間だけスリープすることで、長時間稼働をシミュレートするシンプルなThreadクラスです。
public class MyThread extends Thread{ private int sleepFor; public MyThread(int sleepFor) { this.sleepFor = sleepFor; } @Override public void run() { System.out.printf("[%s] thread starting", Thread.currentThread().toString()); try { Thread.sleep(this.sleepFor); } catch(InterruptedException ex) {} System.out.printf("[%s] thread ending", Thread.currentThread().toString()); }}スレッドクラスのインスタンスを作成し、スリープするミリ秒数を指定します。
MyThread worker = new MyThread(sleepFor);このワーカスレッドの実行は、そのstart()メソッドを呼び出すことで開始されます。このメソッドは、スレッドの終了を待たずに、直ちに呼び出し元に制御を戻す。
worker.start();System.out.printf("[%s] main thread", Thread.currentThread().toString());以下は、このコードを実行したときの出力で、ワーカスレッドが実行される前にメインスレッドの診断結果を出力するよう指示しています。
[Thread[main,5,main]] main thread[Thread[Thread-0,5,main]] thread starting[Thread[Thread-0,5,main]] thread endingワーカスレッドを起動した後は、もうステートメントがないため、メインスレッドはワーカスレッドの終了を待ってプログラムを終了させます。これにより、ワーカスレッドはそのタスクを完了することができます。
また、JavaにはRunnableというインターフェースがあり、これをワーカークラスに実装することで、run()メソッドでタスクを実行させることができます。これは、(上記のように)Threadクラスを拡張するのではなく、ワーカークラスを作成する別の方法です。
以下はWorkerクラスの実装ですが、Threadを拡張する代わりにRunnableを実装するようになりました。
public class MyThread2 implements Runnable { // same as above}Thread クラスを拡張するのではなく、Runnable インターフェースを実装することの利点は、Worker クラスがクラス階層内のドメイン固有のクラスを拡張できるようになったことです。
これはどういうことでしょうか?
例えば、果物のある一般的な機能を実装したfruitクラスがあるとします。ここで、ある果物の特徴に特化したパパイヤクラスを実装したいと思います。papayaクラスがfruitクラスを継承することで、これを実現することができます。
public class Fruit { // fruit specifics here}public class Papaya extends Fruit { // override behavior specific to papaya here}さて、Papayaがサポートする必要がある、別のスレッドで実行できる時間のかかるタスクがあるとします。この状況は、PapayaクラスにRunnableを実装させ、このタスクを実行するrun()メソッドを用意することで対応できる。
public class Papaya extends Fruit implements Runnable { // override behavior specific to papaya here @Override public void run() { // time c***uming task here. }}ワーカスレッドを起動するには、ワーカークラスのインスタンスを作成し、スレッドインスタンス作成時に渡します。スレッドのstart()メソッドが呼ばれると、そのタスクは別スレッドで実行される。
Papaya papaya = new Papaya();// set properties and invoke papaya methods here.Thread thread = new Thread(papaya);thread.start();Runnableを使って、スレッドで実行されるタスクを実装する方法について簡単にまとめました。
Javaはバージョン1.5から、プログラム中にスレッドを生成・管理するための新しいパラダイムとしてExecutorServiceを提供しています。抽象的なスレッドを作成することにより、スレッド実行の概念をカプセル化します。
これは、各タスクに個別のスレッドを使用するのと同様に、スレッドのプールでタスクを簡単に実行できるためです。これにより、プログラムが補助タスクに使用するスレッドの数を把握し、管理することができます。
実行待ちの補助タスクが100個あるとする。もし、各ワーカーに1スレッドずつ起動した場合(上記のように)、プログラムには100スレッドが存在することになり、プログラム内の他の場所でボトルネックを引き起こす可能性があります。その代わり、あらかじめ10個のスレッドが割り当てられたスレッドプールを使用すれば、100個のタスクはこれらのスレッドによって1つずつ実行されるので、プログラムのリソースが不足することはありません。さらに、これらのスレッドプールのスレッドは、他のタスクを実行するためにハングアップするように設定することができます。
ExecutorServiceは、実行可能なタスク(前述)を受け取り、適切なタイミングでタスクを実行します。実行可能なタスクを受け取るsubmit()メソッドは、Futureというクラスのインスタンスを返し、呼び出し側がタスクの状態を追跡できるようにする。特に、get()メソッドでは、呼び出し側がタスクの完了を待つことができます(もしあれば、リターンコードも提供します)。
次の例では、静的メソッド newSingleThreadExecutor() を使って ExecutorService を作成します。このメソッドは、その名が示すように、タスクを実行するためのスレッドを作成します。あるタスクの実行中に複数のタスクが投入された場合、ExecutorServiceはこれらのタスクをキューに入れ、その後の実行を待ちます。
ここで使用する実行可能な実装は、前述と同じである。
ExecutorService esvc = Executors.newSingleThreadExecutor();Runnable worker = new MyThread2(sleepFor);Future<?> future = esvc.submit(worker);System.out.printf("[%s] main thread", Thread.currentThread().toString());future.get();esvc.shutdown();なお、これ以上タスクの投入が必要ない場合は、ExecutorServiceを適切に終了させる必要があります。
Javaでは、バージョン1.5からCallableという新しいインターフェースが導入されました。昔のRunnableインターフェースと似ていますが、実行メソッド(run()の代わりにcall()を呼び出す)が値を返すことができる点が異なります。さらに、例外を投げることができることを宣言することができる。
ExecutorServiceは、callableとして実装されたタスクを受け取り、完了時にメソッドが返す値を使ってFutureを返すこともできる。
以下は、先に定義した Fruit クラスを継承し、callable インターフェースを実装した Mango クラスの例です。高価で時間のかかる作業をcall()メソッドで行っています。
public class Mango extends Fruit implements Callable { public Integer call() { // expensive computation here return new Integer(0); }}以下のコードでは、クラスのインスタンスを ExecutorService に送信しています。また、以下のコードでは、タスクの完了を待ち、その戻り値を表示します。
ExecutorService esvc = Executors.newSingleThreadExecutor();MyCallable worker = new MyCallable(sleepFor);Future future = esvc.submit(worker);System.out.printf("[%s] main thread", Thread.currentThread().toString());System.out.println("Task returned: " + future.get());esvc.shutdown();この記事では、Javaでマルチスレッドコードを書くためのいくつかの方法について学びます。
次のプロジェクトでは、どの選択肢を使うと思いますか?