このページではアプレットでスレッドを使用する方法について 2 つの例を取り上げ る。 最初のアプレット、AnimatorApplet では繰り返しタスクを実行するときのスレッド の使用法を示す。 AnimatorApplet は動画ループを作成するのページに記載されている。 このページで取り上げる 2 番目のアプレット、SoundExample は一度だけの初期化タ スクを実行するときのスレッドの使用法を示す。 SoundExample はサウンドを再生するのページに記載さ れている。
このページでは基本的なスレッドコードについては説明しない。 スレッドの Java 実装を知りたいときは、スレッドのコントロール を参照すること。
繰り返しタスクの実行にスレッドを使用する
同じタスクを何度も実行するアプレットは、そのタスクを実行するwhile (または do...while) ループを伴うスレッドを利用するのが普通である。 典型的な例としては、映画の上映やゲームなど、タイミングを合わせて動画を実行す るアプレットがある。 動画アプレットでは一定間隔でリペイントを要求するスレッドが必要となる。 また別の例としては、サーバ側アプリケーションによって供給されたデータを読み込 むアプレットが挙げられる。 (このような例は、サーバを使用してセキュリティ制約 に対処するで参照できる。)
アプレットは一般に、繰り返しタスクのためのスレッドをそのアプレットのstart() メソッドに作成する。 そこにスレッドを作成すると、ユーザがページを出るとき、スレッドを簡単に停止できる。 アプレットのスレッドを停止するように stop() を実装するだけでよ い。 ユーザがアプレットのページに戻るときは、start() メソッドが再び呼び出され、アプレットは繰り返しタスクを実行するためのスレッドを再び作成する ことができる。
以下に、AnimatorApplet の start() およびstop() メソッドの実装を示す。 (このアプレット全体のソースコードを用意し ている。)
public void start() { if (frozen) { //何もしない。イメージの変動を停止するよう //ユーザから要求されたため。 } else { //動画表示の開始 ! if (animatorThread == null) { animatorThread = new Thread(this); } animatorThread.start(); } } public void stop() { animatorThread = null; }new Thread(this) の this は、このアプレットがスレッドの本体を提供することを表す。 これはjava.lang の Runnable インタ フェースを実装することによって行われる。このインタフェースはスレッドの本体を形成する run() の提供をアプレットに求める。 AnimatorApplet の run() については少し後で説明する。
AnimatorApplet では、start() と stop() メソッドを 呼び出すのはブラウザだけではない。 ユーザがアプレットの表示エリアをクリックして停止させたい動画を指定したときは 、アプレットがそれらのメソッドを呼び出す。アプレットは、frozen インスタンス変数を使用して、ユーザが動画の停止を要求した かどうかを把握する。
AnimatorApplet クラスで Thread stop() メソッドが呼び出されてい ないことに注目してほしい。 Thread stop() メソッドを呼び出すことは、スレッドを無理矢理終了させるようなものだからである。スレッドが実行している処理をスレ ッドに停止させることは荒っぽい方法である。そのかわりに、ユーザがスレッドの肩を ぽんとたたいて優雅に終了させるような、スレッドの run() メソッドを書くことができる。 この方法は、Thread 型のインスタンス変数を null にセットするやり方で実現できる。
AnimatorApplet では、このインスタンス変数の名前はanimatorThread である。start() メソッドが、新たに作成される Thread オブジェクトを参照 できるようにこのインスタンス変数をセットし、 アプレットがスレッドを終了する必要があるときは、animatorThread を null にセットする。 これにより、スレッドはガベージコレクトによって終了するのではなく ( スレッドが実行可能である場合にガベージコレクトすることはできない)、ループのトッ プにおいて、スレッドが animatorThread を調べ、その値に応じて続行か終了かを判定するのである。これに関連したコードを 次に示す。
public void run() { . . . while (Thread.currentThread() == animatorThread) { ...//動画の枠を表示した後、スリープする。 } }animatorThread が現在実行中のスレッドと同じスレッドを指している場合は、このスレッドは実行を続ける。一方、animatorThread が null であれば、スレッドは終了する。animatorThread が別のスレッドを指している場合は、競合状態が発生する。stop() の後、非常に早く start() が呼び出されたため (またはこのスレッドがループの中で長時間を要しているため)、このスレッドがwhileのトップにたどり着く前に start() が別のスレッ ドを作成してしまったのである。競合状態の理由が何であれ、このス レッドは終了すべきである。
AnimatorApplet の詳細については、動画ループを作成するを参照する。
一度だけの初期化の実行にスレッドを使用する
アプレットが時間のかかりそうな初期化タスクを実行する必要があるときは、スレッ ドで初期化を実行するやり方を検討する。たとえば、ネットワーク接続を必要とするような処理は、通常はバックグラウンドのスレッドで行うべきである。 幸い、GIF と JPEG イメージは、(ユーザが考慮する必要のないスレッドを使って) 自動的にバックグラウンドでロードされるようになっている。
一方、サウンドのローディングはバックグラウンドで行われるとは保証されていない。 現在の実装では、Applet の getAudioClip メソッドは、すべてのオーディオデータをロードするまで戻らない。 このため、サウンドをプリロードしたいのであれば、それを実行する 1 つか複数のスレッドを作成するのが望ましい。
アプレットの一度だけの初期化タスクを実行するスレッドを使用することは、基本的 なプロデューサ/コンシューマのシナリオの変形といえる。 つまり、タスクを実行するスレッドがプロデューサ、アプレットがコンシューマなのである。 プロデューサ/コンシューマのシナリオに基づいて Java スレッドを使用する方法については、スレッドの同期で説明している。
SoundExample は、スレッドの同期で示されたモデルと密接につながっている。 スレッドの同期の例のように、 SoundExample は次の 3 つのクラスを特徴づけて いる。
- プロデューサ: Thread サブクラスのSoundLoader。
- コンシューマ: Applet サブクラスのSoundExample。スレッドの同期 でのコンシューマ例とは異なり、SoundExample は Thread ではなく、Runnable イン タフェースを実装することもしない。 しかし、SoundExample インスタンスメソ ッドは SoundExample アプレットを実行するアプリケーションに応じて、少なくとも 2 つのスレッドによって実行される。
- 記憶領域オブジェクト: Hashtable サブクラスのSoundList。スレッドの同期 の例 の CubbyHole とは異なり、SoundList はサウンドデータがまだ格納されていなけれ ば null 値を返すことができる。このことがアプレットにとって意味がある理由は、サ ウンドがまだロードされていなくてもサウンドを再生しようとするユーザ要求にただちに反応する必要があるためである。
SoundExample の詳細については、サウンドを再生するを参照する。