マルチスレッドのいたずらに注意―あなたのJSPやServletは大丈夫?―:事例に学ぶWebシステム開発のワンポイント(4)
本連載では、現場でのエンジニアの経験から得られた、アプリケーション・サーバをベースとしたWebシステム開発における注意点やヒントについて解説する。巷のドキュメントではなかなか得られない貴重なノウハウが散りばめられている。読者の問題解決や今後システムを開発する際の参考として大いに活用していただきたい。(編集局)
今回のワンポイント
コードのロジックに誤りを見つけられないのに、どうも正しく動作してくれない。このようなケースでは、マルチスレッドの影響を疑うべきだ。ServletやJSPはマルチスレッドで動作している(明示的にシングルスレッドで動作させることも可能だが)。そのため、変数の扱いに注意しないと、スレッドごとに値が書き換えられ、プログラムが正しく動作しない要因となる。
マルチスレッドのいたずら
機能確認の試験中は問題なかったのに、複数の端末から負荷をかけてみたらどうしたことか期待どおりに動作しない。プログラムを調査してみても原因が分からなくて途方に暮れる。こんな状況をさまざまなプロジェクトで何度も見掛けてきた。こういう状況になったら、プログラムがスレッドセーフ(複数のスレッドから同時にアクセスされても動作を保証された状態)かどうかを疑ってみることをお勧めする。
マルチスレッドに関する問題は、さまざまな書籍や雑誌で紹介されており、いまさらと思う話題なのであるが、一度ソースを作成してしまうとなかなか見つけづらいのも事実である。J2EEの利点として、スレッド周りのシステムプログラミングが不要なので、JSPやServletがスレッドで動いていることを忘れがちだ。納期に追われて、結局スレッドを意識することを忘れてしまうことも多いだろう。そこで今回は、スレッドセーフにするためのポイントを紹介する。
クラス変数には要注意
クラス変数というのは、static節で宣言したクラス自体の変数である。スレッドセーフを意識しない人の中には、クラス変数とインスタンス変数の違い自体を理解していない人もしばしば見掛ける。こんな状況だと、インスタンス変数でよいところをなぜかstatic宣言にしてしまったりする。また、中にはキャッシュの効果を狙いstatic宣言にしているパターンもある。
実際にあった例を見てみよう。この例では、システム独自の付加情報が付いたディレクトリ一覧を取得する必要があり、別のクラスとして共通化していた。そして、そのクラス内でディレクトリ一覧を格納するVectorをstatic変数として作成していたのである。しかし、static変数はスレッド間で共有オブジェクトとなるため、複数のスレッドから同時にこのVectorを更新してしまうことがある。つまり複数のクライアントから同じServlet/JSPへ同時にアクセスすると、スレッド間でVectorの値が保証できないという問題が発生する。
実際、この例では、単体試験や機能確認試験ではまったく問題はなかったのであるが、幸い負荷試験中にうまく動作しないことが発覚し、Vectorをインスタンス変数とすることで(static節を外すだけ)、めでたく動作したのである。
インスタンス変数にも注意せよ
インスタンス変数といってもいろいろあるが、ここで話題にするのはServletやJSPのインスタンス変数である。まずは実際にあったソースコードを紹介しよう。
この例では、マルチスレッドモデルでServletを動作させていたため、スレッド間でインスタンス変数paramが共有され問題となったのである。つまり、複数の端末から同時にServletへアクセスすると、paramの値がスレッドごとに書き換えられてしまい、タイミングによっては、getParameters()メソッドを実行したときのparamの値と、その後に呼ばれるexecute()メソッドやdisplayPage()メソッドでのparamの値が異なる場合があるのである。このソースコードだけ見ていると特に不自然には感じないため、なかなか見つけるのが難しい誤りである。このケースでは、ソースレビューを行う段階で発覚し、Servletのインスタンス変数を使用しないようにすることで対処を行った。
さて、JSPではどうだろうか? あるシステムでは運用初期から不具合報告が挙がっていたが、再現性もないためその原因を特定することができない問題があった。実際不具合個所には、static変数やServletのインスタンス変数を利用しているわけでもなく、プログラム的には何の問題もないと思い込んでいた。しかし、よくよく調査してみるとJSPには以下のようなタグが多用されていた。
<%! String beforePageName = request.getParameter ("beforePageName") %>
<%! %>タグは、変数の宣言やメソッド宣言時に使用するものである。しかし、JSPがコンパイルされるとServletのインスタンス変数として展開されてしまうのである。つまり、上記Servletの例がそのまま当てはまることになってしまうのである。不幸なことにこのシステムでは、JSP内の変数定義はこのタグを用いるようにと規約化していた。さらに、デバッグ環境では、単体レベル(1ユーザーのみ)で試験をしていたため再現性がなかったのである。
規約化した時点で気付かなかったことと、マルチスレッドを意識しない試験のため、なかなか発見できなかった実例の1つである。
Servletのシングルスレッドモデルとは?
特に指定しない限りServletはマルチスレッドモデルで実行されるが、明示的にシングルスレッドモデルで動作させることもできる(javax.servlet.SingleThreadModelにより実装可能)。これは、Servletのインスタンスとスレッドが1対1にマッピングされるモデルであり、Servletのインスタンス変数はスレッドセーフとなる。Servletのインスタンス変数問題を解決するために、シングルスレッドモデルを採用することも考えられるが、その分Servletの並列性が下がることや、余分なインスタンスが作成される可能性もあるため、利用しない方が望ましい。
スレッドセーフのポイント
今回紹介したマルチスレッドの問題は、J2EEのプログラムを実装するうえでは基本中の基本といってよい。しかし、多くのプロジェクトで悩みの種となっていることも事実である。これは、スレッドという性質上、問題が再現しにくいということが大きな問題として挙げられるが、Visual BasicやJavaアプリケーションのような単体で動作するAPの経験で染み付いたコーディングスタイルがあだとなっていることもあるであろう。
マルチスレッドの問題はここで紹介した以外にもいろいろなケースが考えられるが、まずは、ServletやJSPがマルチスレッドとして動いているのだということを常に意識することが重要である。また、開発の際は、以下のことも考慮してみることをお勧めする。
- 今回紹介したクラス変数や、Servletのインスタンス変数については、設計段階から使わないようにするなど規約化を行ってから開発すること
- 規約に沿って第3者という観点でソースレビューを行うこと
- 負荷試験を行うなど、チェック手法も確立すること
著者プロフィール
重畠 洋二(しげはた ようじ)
株式会社NTTデータCOEシステム本部に所属。 技術支援グループとして、J2EEをベースにしたWebシステム開発プロ ジェクトを対象に、技術サポートを行っている。
- ファイルアップロード/ダウンロードに潜むわな− 大容量、高負荷時の注意点 −
- ブラウザキャッシュでパフォーマンス向上―負荷分散装置の落とし穴に注意−
- JDBC接続を高速化する− PreparedStatementキャッシュの威力−
- レスポンスキャッシュでパフォーマンス向上―アプリケーションを変更せず性能UPする魔法のつえ―
- メモリは足りているのに“OutOfMemory”
- 文字化け“???”の法則とその防止策
- 低負荷なのにCPU使用率が100%?
- APサーバからの応答がなくなった、なぜ?―GCをチューニングしよう―
- サービス中にアプリケーションを入れ替える―クラスタ機能を活用しよう―
- マルチスレッドのいたずらに注意―あなたのJSPやServletは大丈夫?―
- クラスタは何台までOK?―性能から見たWebLogicクラスタの適正台数―
- キャッシュが性能劣化をもたらす“なぞ”を解く― データのキャッシュとコネクションプール ―
- クラスタ化すると遅くなる?― HttpSessionへの積み過ぎに注意 ―
Copyright © ITmedia, Inc. All Rights Reserved.