スレッド対応

 


 


サーブレットにおけるスレッド安全とは

 

皆さんが開発したサーブレットが実際にコンテナに配備(デプロイ)されると、エンジンはクライアントからの要求をスレッドとして、要求と応答のオブジェクトを連れて皆さんのサーブレットのserviceメソッドに送りこむ。クライアントからの要求ごとにスレッドは生成される。スレッド間のタイミングはなく、皆さんのservice(さらにそれが呼ぶdoPostdoGetなど)メソッドには複数のスレッドが走ることになる。そのような状態においても問題が無い(スレッド・セーフという)サーブレットが開発されねばならない。

 


 


サーブレットがパラメタとして持ち込まれたHttpServletRequestHttpServleResponseのオブジェクトやserviceメソッド内でルーカルに宣言した変数やパラメタを使っている限りスレッド・セーフであり、心配する必要が無い。HttpServletRequestHttpServleResponseのオブジェクトは参照(云ってみればポインタ)としてスレッドが持ってきたものであり、またメソッド内のローカル変数は各スレッド毎に割り当てられる(低レベル言語ではリエントラントな構造という)からである。しかしながらそのサーブレットで宣言したインスタンス変数やクラス変数は各スレッドに共有される。そのサーブレット外のデータベース、ヘルパーなどの資源もスレッドが共有する。その場合はスレッド間の干渉には十分な注意が必要である。

 

サーブレットAPIにはSingleThreadModelというインターフェイスが用意されている。このインターフェイスを実装(implement)すると、下図のようにエンジンはコンテナ上にそのサーブレットの複数のインスタンスのプールを用意し、ひとつのサーブレットのオブジェクトにはひとつのスレッドしかアクセスしないよう制御する。

 


 


何個のインスタンスを生成するか(WebSpereでは4個以上であった)、それ以上のHTTP要求がきたらどうするか(多分待ち行列に入れる)はサーブレット・エンジンの実装の問題である。こうすればインスタンス変数の共有問題は解決できるが、それ以外の外部資源の共有問題の解決にはならないことに注意しよう。

 

またサーブレット仕様書はservice(さらにそれが呼ぶdoPostdoGetなど)メソッドに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

 

 

前節     目次     次節