- - PR -
ServletのdoGet, doPostはsynchronizedが必須!?
投稿者 | 投稿内容 | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2004-12-17 10:27
サーブレット内で以下の処理でサーブレットでセッション変数の一覧を取得しようとした所、
java.util.ConcurrentModificationExceptionが発生しました。 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { . . . HttpSession session = request.getSession(); Enumeration names = session.getAttributeNames(); while(names.hasMoreElements()) { Object o = names.nextElement(); // <-------- ここでConcurrentModificationExceptionが発生した。 if (o instanceof String) { System.out.println((String)o); } } . . . } これは、たまたまこれと同じタイミングで同じセッションに対してセッション変数の追加、または削除が行なわれたために発生したものでしょう。 そこで疑問が出てきました。 ConcurrentModificationExceptionを避けるためには、上記のsessionを使ってsynchronizedをすればいいでしょう。 しかし、セッション変数の一覧を取得する処理は多数あります。 また、sessionに対するsetAttribute, removeAttributeを実行する処理も同じく多数あります。 そうすると、synchronizedを使う場所が多数でてきます。 セッション変数の操作でgetAttribute以外の処理を行なう場所はほぼ全てsynchronizedをつける必要があります。 synchronizedを多用するとそれだけパフォーマンスが落ちると推測しています。 ならば、どうせならばdoPost全体にsynchronizedを設定したほうが早いように思われます。 多重フレームで動かせば同一セッションへのアクセスは発生するでしょうし、小刻みなクリックによる同一セッション内での サーブレットの二重起動もあるでしょうから、同一セッションへの多重アクセスは避けれないと思われます。 となると、 「セッション変数の一覧の取得を行なうサーブレットは必ずdoPostの内部全体をsessionによるsynchronizedを行なう必要が出てくる。 さもないと、場合によってはConcurrentModificationExceptionが発生する可能性がある」 と、なります。 これは正しいでしょうか? それとも、ConcurrentModificationExceptionを発生させないでセッション変数一覧を取得する方法があるのでしょうか? これが正しいとすると、何故、多重アクセスでエラーとなるようなAPIがHttpSessionに存在するのか不思議になってきます。 | ||||||||||||||||||||
|
投稿日時: 2004-12-17 11:18
こんなかんじでいいのでは。
問題は、Sessionやその属性に対する多重アクセスじゃないと思います。 Enumeratorに格納された参照先が消滅した、ということですよね。 逆に、Enumeratorを取得した後、Sessionに、新たな属性が追加される場合もあり、 その場合は、すでに取得したEnumeratorからは参照できないことですし。 | ||||||||||||||||||||
|
投稿日時: 2004-12-17 11:27
HttpSessionはインターフェイスですので、 多重アクセスでエラーになるかは実装次第です。 HttpSessionを実装したクラスが返した、 Enumerationを実装したクラスが たまたまConcurrentModificationException を投げる作りになっていたと言うことです。 これはすべてのServletに当てはまりません。 取りあえず対処するならEdossonさんの方法でよいかと思います。 | ||||||||||||||||||||
|
投稿日時: 2004-12-17 12:32
とドキュメントに明記されている限りは、try-catchで拾うのは避けるべきかと思われます。 そもそもdoPostメソッドで行なわれている処理が、並行処理を「是」としているのかどうか そのへんが重要になるのではないでしょうか。 「是」とするならば、今回のようにConcurrentModificationExceptionが発生しうる箇所 のみをsynchronizedブロックで囲めば良いでしょう(発生箇所が多く、synchronizedによる オーバーヘッドが並行処理のメリットを上回るのならば、この限りではありませんが) 「否」とするならば、doPost全体をsynchronizedにするか、または他の方法でメソッドへの 進入を拒むべきかな、と思います。
それを言っちゃうと「何でHashMapなんてスレッドセイフじゃないコレクションをSunは新たに 用意したんだ?」って事にもなっちゃいます。 時として「必要な事」が、或る時には「余計な事」になっちゃったりするもんです。 | ||||||||||||||||||||
|
投稿日時: 2004-12-17 13:59
返答ありがとうございます。
>>Edossonさん、かつのりさん なるほど、エラーが出ても無視すればいいわけですね。 ただ、削除されたセッション変数名だけがなくなればいいのですが、削除されたもの以外のセッション変数名まで取得できなくなる可能性はあるような気がします。 発生する可能性が少ないので、全体に無限ループをかけて、
のように成功するまでループさせようかとも考えたのですが、長期間ループを抜けない、またはいつまでたってもループを抜けなくなる可能性を考えて躊躇しています。 >>takamaroさん
ConcurrentModificationExceptionが発生しうる箇所だけならばいいのですが、このエラーを引き起こすHttpSessionのsetAttribute, remoteAttributeメソッドにも同じようにsynchronizedブロックをかける必要がありますよね? セッション変数の操作はサーブレットの基本動作ですから、かなり多数になると思います。 コーディング量も気になるのですが、頻繁にsynchronizedをするためにパフォーマンスが落ちるような気がしています。
うーん、やはり、そうなりますよね。
なるほど。synchronizedを自分でつけることは可能だけれど、はずすとなると別クラスを用意する必要がでてきますね。 | ||||||||||||||||||||
|
投稿日時: 2004-12-17 14:25
「削除されたもの以外」で、かつ取得できないセッション変数って具体的にどんなものですか? 内部でシステムがどんな使い方をしているのか知れたものではないとなれば、 それが本当に対処しなければならないものなのかどうか、の見極めも必要と思いますが。 | ||||||||||||||||||||
|
投稿日時: 2004-12-17 14:30
通常セッションは各ユーザ単位で発行される物であって、
同一セッションに対して非同期で動くことはあまりありえないケースですよね。 (IEでCtrl+Nを押して同じ画面を2つ開く等・・・) ならばHttpServletのserviceメソッドをオーバーライドして
みたいに記述すれば、同一セッションでのみスレッドセーフになります。 この記述をしたサーブレットをスーパークラスとするか、 コントローラとして使用すれば ある程度安全に運用は可能じゃないかなと思います。 同期コストは必要ですが、マルチスレッドで動くので パフォーマンスも悪くないはずです。 (ただし一部のサーブレットコンテナでは getSession().toString().intern()で同期を行わなければいけませんが。) | ||||||||||||||||||||
|
投稿日時: 2004-12-17 15:03
セッション変数の値ではなく、一覧から取得するセッション変数名です。 前述のコードでも、セッション変数の値は全く考慮していなくて、セッション変数名だけを処理することを考えています。 例えば、セッション変数の名前として"V1", "V2", "V3", "V4", "V5"があったとします(値はここでは触れません)。 「Enumeration names = session.getAttributeNames(); 」を実行してセッション変数名(セッション変数の値ではない)の一覧を取得した後 このEnumeration namesからセッション変数名をnextElement()で順次取り出している時、 "V1"の名前を取り出した直後に別のスレッドで「session.removeAttribute("V3")」が行なわれた場合、どうなるか、を考えています。 "V3"まで読み進んでからエラーになる? それとも、Enumeration names自体が無効になり、次の"V2"の名前を取得する時にエラーになる? "V3"はエラーになるとして、次の"V4"はエラーにならない? 最悪の場合、"V2"の名前の取得でエラーになることも考えなければいけないし。 "V4"以降でもエラーが発生することも考えなければならないかな、と思っています。 |