JSP/Servlet をスレッドセーフにする

Java Servlet はサーブレット・コンテナ(Tomcat が有名)によってマルチスレッド環境で起動される。これにより,マルチプロセスの高コストを回避して,高速の並列処理を実現している。ここで,Java Web プログラミングにおいてもっとも注意しなければならない事項のひとつとして,コードがスレッドセーフに書かれているかどうかということがある。スレッドセーフでない,すなわち,スレッドハザードを起こすというのは,たとえば,あるスレッドで預金残高を計算した結果を別のスレッドが書き換えてしまうような並列処理の問題を言う。今日は,JSP/Servlet をスレッドセーフにするポイントを備忘録として整理しておく。

  1. Java Servlet のプログラミングにおいては,クラス変数,インスタンス変数を原則使ってはならない。

    クラス変数,インスタンス変数の例は以下のようなものである。

    public class sampleServlet extends HttpServlet {
        static int iValue;       // クラス変数
        HttpServletRequest wReq; // インスタンス変数
      
        public void doPost(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException
        {
            wReq = req;
            ...
            if (iValue > 1000) {
                wReq.setAttribute("hoge", "NG");
                iValue = 1000;
            } else {
                wReq.setAttribute("hoge", "OK");
            }
            ...
        }
    }

    クラス変数というのは static の付いた変数である。そのクラスがいくつインスタンス化されようとただひとつだけメモリ上に存在し共有される変数である。これが実行過程で更新されるロジックになっているとスレッドハザード状態となる。上の例では,あるユーザからみて知らぬ間に iValue が 1000 に更新され,結果として誤動作する危険性がある。

    インスタンス変数は当該クラスのインスタンス化されたオブジェクト内の変数だが,マルチスレッド環境ではスレッド間で共有となる。複数のスレッドで更新するロジックになっているとスレッドハザード状態となってしまう。上の例では,タイミングによっては wReq に他人のリクエストを格納してしまう。

    ただし,final が付加され初期化以後書き換えができない変数,init() メソッドで初期化した後に更新されない変数はこの限りではない。また,システム一意のシーケンス番号(処理のたびにカウントアップして全体でどれだけ仕事をしたかを管理するようなデータ)として意図的にクラス変数を用いる場合もある。

  2. JSP スクリプト宣言部には,更新される可能性のある変数を定義してはならない。

    JSP スクリプト宣言部は JSP コンテナによってコンパイルされると,サーブレットのインスタンス変数に展開され,上記とまったく同じ問題に嵌るからである。

    <%! String uid = request.getParameter("user_id"); %>

    <%! がスクリプト宣言部での定義を示す。やってはならないコーディング例である。初期化をともなう変数は,スクリプト宣言部ではなく,次のようにスクリプトレット部で宣言しなければならない。

    <% String uid = request.getParameter("user_id"); %>
  3. ServletContext は原則使用してはならない。

    ServletContext は Web アプリケーション全体で共有され,サーブレットコンテナが停止するまで存続する。あるユーザが自分のロジックのために ServletContextsetAttribute() で設定した値は,別スレッド(他のユーザ)で書き換えられてしまう可能性があり,意図した動作をしなくなる。

  4. JSP <jsp:useBean /> のアクション属性 scope に application を使用してはならない。

    これはデータの格納先が ServletContext になるためである。


以上は JSP/Servlet プログラミングをスレッドセーフにするための鉄則である。コーディングとしては可能でもやってはならない,という意味である。

多くのユーザが利用する Web アプリケーションで,ユーザ A の入力データがユーザ B の画面に出てしまう,というような恥ずかしいバグは,たいてい,スレッドセーフを考えないヘボ・プログラマ(私たちは「アマグラマ」と呼んでいる)の手になるものと断言してよい。プログラムは少しの訓練で誰だって書けるようになる。ところが,アルゴリズムとデータ構造を熟知し,ソフトウェアのメーカーが蓄積した生産技術を習得してはじめて,「仕事」で使えるプログラムを書くことができるプロになる。計算機技術は易しくない。

ブログ記事や,得体の知れない日本人の書いたプログラミング入門書籍(O'Reilly など,対象を開発した技術者が著者になっているような書籍を除く)には,上記 1〜4 を無視したヘボ・コードを例として掲げている場合があるので,注意すべきである。それらヘボ・コードは動くことが第一条件であることが多く,参考程度にするに停め,「そのまま拝借する」のは絶対にしてはならない。

プロフェショナルのプログラマならば,ブログ記事やプログラミング一般書籍ではなく,いま担当するプロジェクトのコーディング基準書に則ってコードを書くべきである。コーディング基準書はあらゆるまともなソフトウェアプロジェクトには必ずある。そしてその基準書は,コンピュータメーカー,ソフトウェアベンダなどの組織が長い経験の過程で蓄積した生産技術に基づいているものである。

ブログ記事のノウハウを信じるなとブログで書いている私は,— つまり終わっている。