スレッド対応
|
|
サーブレットにおけるスレッド安全とは
皆さんが開発したサーブレットが実際にコンテナに配備(デプロイ)されると、エンジンはクライアントからの要求をスレッドとして、要求と応答のオブジェクトを連れて皆さんのサーブレットのserviceメソッドに送りこむ。クライアントからの要求ごとにスレッドは生成される。スレッド間のタイミングはなく、皆さんのservice(さらにそれが呼ぶdoPostやdoGetなど)メソッドには複数のスレッドが走ることになる。そのような状態においても問題が無い(スレッド・セーフという)サーブレットが開発されねばならない。
サーブレットがパラメタとして持ち込まれたHttpServletRequestやHttpServleResponseのオブジェクトやserviceメソッド内でルーカルに宣言した変数やパラメタを使っている限りスレッド・セーフであり、心配する必要が無い。HttpServletRequestやHttpServleResponseのオブジェクトは参照(云ってみればポインタ)としてスレッドが持ってきたものであり、またメソッド内のローカル変数は各スレッド毎に割り当てられる(低レベル言語ではリエントラントな構造という)からである。しかしながらそのサーブレットで宣言したインスタンス変数やクラス変数は各スレッドに共有される。そのサーブレット外のデータベース、ヘルパーなどの資源もスレッドが共有する。その場合はスレッド間の干渉には十分な注意が必要である。
サーブレットAPIにはSingleThreadModelというインターフェイスが用意されている。このインターフェイスを実装(implement)すると、下図のようにエンジンはコンテナ上にそのサーブレットの複数のインスタンスのプールを用意し、ひとつのサーブレットのオブジェクトにはひとつのスレッドしかアクセスしないよう制御する。
何個のインスタンスを生成するか(WebSpereでは4個以上であった)、それ以上のHTTP要求がきたらどうするか(多分待ち行列に入れる)はサーブレット・エンジンの実装の問題である。こうすればインスタンス変数の共有問題は解決できるが、それ以外の外部資源の共有問題の解決にはならないことに注意しよう。
またサーブレット仕様書はservice(さらにそれが呼ぶdoPostやdoGetなど)メソッドにsynchronizeキーワードを付して同期化しないよう強く勧告している。確かに入り口のメソッドを同期化すればそこでスレッドは直列化されるが、エンジンがそのインスタンスのプールを用意しないので、スループットが大きく落ちてしまうからである。
サーブレット・エンジンが何個までのインスタンスを生成したかを知る簡単なサーブレットを紹介しよう。プログラム・リストを見ていただければ判ると思うが、static Hashtable instancesなる変数は現在生成されてコンテナ上に配備されているインスタンスの数を蓄積するテーブルである。各インスタンスは自分がインスタンス化されたときにinitメソッドにて自分自身をこのテーブルに登録する。反対に削除されるときはdestroyメソッドにて自分自身の登録を削除する。従ってこのテーブルに何個のオブジェクトが登録されているかでコンテナ内のこのサーブレットのインスタンス数を知ることができる。
HowManyInstancesサーブレット |
package
basic_servlets; import
java.io.*; import
java.util.*; import
javax.servlet.*; import
javax.servlet.http.*; /** * このサーブレットは呼び出されるたびにその呼出回数とインスタンス数を返す * 作成日 : (01/07/05 13:26:15) * @author: Terry */ public class
HowManyInstances extends HttpServlet implements
SingleThreadModel { static int totalCount = 0; // 全てのインスタンスが共有するアクセス回数カウンタ int count = 0;
// 各インスタンス毎の呼び出された回数カウンタ static
Hashtable instances = new Hashtable(); // 全てのインスタンスが共有するインスタンス計数用Hashtable public void
doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { performTask(request,
response); } public void
doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { performTask(request,
response); } public String
getServletInfo() { return
"HowManyInstances, Version 1.0 by Terry"; } /** * クライアントにアクセス回数と現在のインスタンスの数を報告する */ public void
performTask(HttpServletRequest request, HttpServletResponse response) { try { response.setContentType("text/html;
charaset=Shift_JIS"); PrintWriter out =
response.getWriter(); out.println("<HTML><HEAD><TITLE>HowManyInstances</TITLE></HEAD>"); out.println("<BODY><H1>HowManyInstancesサーブレット</H1><PRE>"); count++; out.println("このサーブレットのインスタンス: " +
this.toString()); out.println("インスタンス化されてからの呼び出し回数: " +
count); out.println("コンテナ内のインスタンスの数: " +
instances.size()); totalCount++; out.println("インスタンス全部がアクセスされた回数: " +
totalCount); out.println("</PRE><BODY>"); out.flush(); out.close();
response.flushBuffer(); //10秒間このスレッドを待たせ、このインスタンスをビジーにする //この間別のブラウザからこのサーブレットをアクセスして実験する Thread.sleep(10000L); } catch(Throwable
theException) { } } /** * サーブレット・エンジンはインスタンスの削除にあたってこのメソッドを呼ぶ。 * 自分自身をHashtableから削除してコンソールに表示 */ public void
destroy() { instances.remove(this); System.out.println("インスタンス削除: " +
this.toString()); System.out.println("インスタンスの数: " +
instances.size()); super.destroy(); } /** * サーブレット・エンジンはインスタンスの生成にあたってこのメソッドを呼ぶ * 自分自身をHashtableに登録してコンソールに表示 */ public
synchronized void init() { // Hashtableは同じキーで登録されたものは更新されるだけなので // 自分をキーとして自分を登録するとこのクラスのインスタンスの総数を示すことになる instances.put(this, this); System.out.println("インスタンス生成: " +
this.toString()); System.out.println("インスタンスの数: " +
instances.size()); } } |
このサーブレットをスレッドが通過するのに少なくとも10秒かかるので、その間に新たな要求をサーブレット・エンジンに送り込めば、エンジンは別のインスタンスにあらたなスレッドをわりあてることになる。皆さん各自自分のサーブレット・エンジンを使って試していただきたい。
以下は、WTEの環境でこのサーブレットをCRT上に6個のIEのウインドウを配置して、10秒以内に立て続けに各ブラウザからアクセスした事例である。サーブレット・エンジンが立ち上がった時点で4個のインスタンスが既に生成されているにも関わらず、二つのインスタンスが追加され、次に@1970のインスタンスが削除されている。この場合はインスタンスの数は10まで増加した。しばらく放置した後にアクセスすると5個のインスタンスが立て続けに削除された。このようにSingleThreadModelを実装したサーブレットのインスタンスの管理と要求スレッドの送り先は、サーブレット・エンジンがダイナミックに行っているので、エンジンの特性を良く調べてアプリケーションのスループットを確認する必要がある。
WebShere(WTE)での実験例 |
***Servlet エンジンは再始動されました*** urilist:
/ErrorReporter/*=ErrorReporter /servlet/*=invoker *.jsp=jsp /=file /HelloWorld/*=HelloWorld /HttpRequestDump/*=HttpRequestDump /AnotherHelloWorld/*=AnotherHelloWorld /ChunkedHelloWorld/*=ChunkedHelloWorld /CompressedHelloWorld/*=CompressedHelloWorld /ReturnStatusCode/*=ReturnStatusCode /CompressedFileTransfer/*=CompressedFileTransfer /SimpleSessionTest/*=SimpleSessionTest /SimpleMessageManager/*=SimpleMessageManager /SimpleMessageReceptionist/*=SimpleMessageReceptionist /HowManyInstances/*=HowManyInstances インスタンス生成:
basic_servlets.HowManyInstances@1fbf インスタンスの数: 5 インスタンス生成:
basic_servlets.HowManyInstances@1970 インスタンスの数: 6 インスタンス削除:
basic_servlets.HowManyInstances@1970 インスタンスの数: 5 インスタンス生成:
basic_servlets.HowManyInstances@3a46 インスタンスの数: 6 インスタンス生成: basic_servlets.HowManyInstances@4435 インスタンスの数: 7 インスタンス生成:
basic_servlets.HowManyInstances@ed2 インスタンスの数: 8 インスタンス生成:
basic_servlets.HowManyInstances@6906 インスタンスの数: 9 インスタンス生成:
basic_servlets.HowManyInstances@ca5 インスタンスの数: 10 (しばらくここで放置した) インスタンス削除:
basic_servlets.HowManyInstances@1fbf インスタンスの数: 9 インスタンス削除:
basic_servlets.HowManyInstances@3a46 インスタンスの数: 8 インスタンス削除:
basic_servlets.HowManyInstances@4435 インスタンスの数: 7 インスタンス削除:
basic_servlets.HowManyInstances@ed2 インスタンスの数: 6 インスタンス削除:
basic_servlets.HowManyInstances@ca5 インスタンスの数: 5 |