スレッドセーフとは,アプリケーションをマルチスレッドで動作させても問題がないことを指す。サーバー向けアプリケーションは,マルチスレッドで動作するように設計・実装することが望ましい。そのほうが通常はパフォーマンスが向上するからだ。

 だが,マルチスレッドのアプリケーションは,注意深く設計・実装しないとトラブルが生じる。例えば,あるスレッドで保持していた変数の値がほかのスレッドからアクセスされ,処理結果が上書きされたり,ほかの利用者の情報が見えてしまったりする。

 こうしたトラブルは,開発者が1人で単体テストしているときには見つけられず,多数の利用者で限界時の挙動テストをしたときや,本番移行した後で,たまたま見つかることが多い。トラブルが発生するタイミングを再現することが難しいので,デバッグは困難になりがちだ。

 マルチスレッドでのトラブルを防ぐため,開発者は,スレッドセーフな設計と実装を心がける必要がある。

Javaではローカル変数のみ安心

 マルチスレッドで動作するアプリケーションでは,一つのプロセスで複数の利用者からのリクエストを同時に受け付け,処理するといったことが可能になる。いちいちプロセスを起動する必要がないのでパフォーマンスが向上するし,CPUやメモリーなどを効率よく使える。最近主流になってきたマルチコアCPUでは,複数のコアで各スレッドを同時実行できる。今後は,マルチスレッドで動作するアプリケーションの開発が,従来以上に一般的になると考えられる。

 マルチスレッドでの動作を,JavaのServletとJSP(JavaServer Pages)を例に説明しよう(ServletとJSPはマルチスレッド動作を前提としている。シングルスレッド動作に切り替えることもできるが,一般には推奨されていない)。Servletでは,変数としてクラスで共用される「クラス変数」,クラスから生成された個々のインスタンス(オブジェクト)で使用される「インスタンス変数」,メソッド内やブロック内で使われる「ローカル変数」を利用できる。

 3種類の変数のうち,スレッドセーフなのはローカル変数だけ()。ローカル変数はスレッド固有のメモリー領域である「Javaスタック」に保持されるので,一つのスレッドからしかアクセスされない。スレッドごとに固有の変数格納庫があることになり,ほかのスレッドに情報を書き換えられたり,ほかのスレッドが情報を取り違えて参照したりすることはない。

図●ローカル変数以外は複数のスレッドから共有され得る
図●ローカル変数以外は複数のスレッドから共有され得る
Javaの変数は,ローカル変数だけがスレッドセーフ(複数のスレッドが同時に動作しても異常をきたさないこと)である。その理由は,ローカル変数はスレッドごとに割り振られる「Javaスタック」と呼ぶメモリー領域に格納されるからだ。これに対して共有メモリーである「ヒープ領域」に格納されるインスタンス変数やクラス変数は,複数のスレッドが共有する可能性があり,スレッドセーフではない。そのため,参照または更新するときにはロック(更新メソッドにsynchronizedキーワードを指定する)などを検討しなければならない
[画像のクリックで拡大表示]

 一方,クラス変数とインスタンス変数は,共有メモリー領域である「ヒープ領域」に保持される。ヒープ領域は複数のスレッドに共有されているので,情報を書き換えられてしまう場合がある。つまり,スレッドセーフではない。クラス変数とインスタンス変数は,可能な限り使わないようにするか,やむを得ず使う際には,ほかのスレッドの影響を受けないように実装する必要がある。具体的には,ほかのスレッドの実行完了を待つようにするか,変数などをロックしてスレッド間で排他制御する必要がある。マルチスレッドのプログラムを開発したりデバッグしたりすることはかなり面倒である。

 JSPの場合,開発者がソース上で変数の種類を意識することは少ないだろうが,変数やメソッドの宣言に使われる<%! %>タグが,Servletへのコンパイル時にインスタンス変数に展開されている。このため実際には,Servletと同様にマルチスレッドの考慮が必要になる。

Strutsを使う場合の注意点

 他人が作ったミドルウエアやライブラリを利用する場合には,スレッドセーフであるかどうかを事前に確認しよう。マルチスレッド・プログラミングは面倒なので,できるだけスレッドセーフであることが保証されているものを利用する。

 使いたいライブラリがスレッドセーフでなければ,マルチスレッドを意識しなくてもよくなるように工夫する。例えば,MVC(Model/View/Controller)アーキテクチャに基づくフレームワーク「Apache Struts」の場合,Actionクラスはスレッドセーフではない。そのため,Actionクラスを継承してロジックを実装してしまうと,開発者はマルチスレッドを意識しなければならない。

 こうしたケースでは,例えば,「DI(Dependency Injection)コンテナを活用し,リクエスト(=スレッド)ごとにインスタンスが生成されるようにする。こうすればスレッド間でインスタンス変数が共有されなくなる」(アクシスソフト ソリューション事業部 Webソリューション部 第2グループ システムエンジニア 高木義昭氏)。これにより,マルチスレッドを意識しなくてもよくなる。