Java サーブレットのメンバ変数を使ってはいけない




Webアプリケーションは、「複数のユーザーが同時に利用する」ことがふつうです。
Tomcat等のWebアプリケーションコンテナは、複数のユーザーが処理を同時に実行できるように、マルチスレッドで並列に処理を実行します。
当然ですが、このコンテナ内で動作するサーブレットはマルチスレッドで実行されるため、他のユーザーが処理している内容に干渉しないようにする必要があります。

Java サーブレットのメンバ変数を使ってはいけないサンプル

ブラウザより、xとyの引数を受けて、足し算した結果をブラウザに返します。
計算した結果は、サーブレットのメンバ変数に格納しておきます。
また、同時実行しやすいように、5秒間処理を待たせてみます。

実行結果

2つのブラウザより、ほぼ同時(5秒以内)にアクセスします。
ブラウザ1つ目 : x = 1 , y = 2

ブラウザ2つ目 : x = 2 , y = 2

予想に反して、どちらのブラウザでも2つ目に実行した値が返ってきました(; ・`д・´)
簡単な足し算なのにコンピュータでも間違えるんですねw

サンプルの解説

サーブレットのインスタンスは、スレッドごとに複数生成されません。
つまり、どちらのブラウザも同じインスタンスを利用して実行されていたため、メンバ変数の値が共有されてしまい、後から実行されたブラウザ2つ目の計算結果が上書きしてしまったのです。
※もちろん、使い方によっては、サーブレットでメンバ変数を使って良いのですが、ありがちな例です。

サンプルの対策

大きく2つあります。

  • メンバ変数をやめて、doGetメソッド内のローカル変数にします。
  • サーブレットでは処理をせず、別のクラスにメンバ変数・処理を移植。別のクラスをnewして使う。

そんなのめんどい。SingleThreadModelにすればいんじゃね?

javax.servlet.SingleThreadModelをインプリメントすると、そのサーブレットは同時実行されなくなります。
よって、この現象は発生しなくなります。
ですが、「そのサーブレットは同時実行されなくなる」ため、誰かが実行中だと待つことになります。
このサンプルのように2つのブラウザで実行すると、2つ目は最大10秒待つことになります。
みんなが使うサーバーなのに、これでは意味がありません。
※SingleThreadModelインタフェースは、Java Servlet API 2.4以降、非推奨になっています。