J2EEでのWebアプリケーション開発では、簡素なルールを守りさえすればそれほど同期処理を考える必要はありません。
- 各クラスにstaticな可変のフィールドを作らない
- Servletの実装クラスにインスタンスフィールドを作らない
- セッションを用いない
概ね、上記を守っていれば複数のユーザが並列にHTTPリクエストを投げたところで同期に起因するバグは発生しない。
DBへのアクセスは当然ながらDB側でトランザクション管理されているものとします。
セッションには同期が必要
さて本題のセッションですが、こいつを使う場合、並列してHTTPリクエストが送られてきた場合を想定する必要があります。
セッションは同一ユーザからのアクセスで同じものが参照できるわけですから、ユーザがブラウザから複数のHTTPリクエストを
発すると複数のスレッドから同時にセッションが参照されることになります。
そうなると、スレッドを扱う際の問題と同じことがおきます。
例えば以下のコード、セッションからキー"test"で値をとってきて、+1して格納しなおすだけですが、
HttpSession session = request.getSession();
Integer i = (Integer) session.getAttribute("test"); // (A)
i = new Integer(i.intValue() + 1); // (B)
session.setAttribute("test", i); // (C)
ふたつのリクエスト(以下ではR1、R2と呼びます)から、ソースのA、B、Cが
R1 |
R2 |
(A) |
|
↓ |
(A) |
(B) |
↓ |
↓ |
(B) |
(C) |
↓ |
|
(C) |
というタイミングで動くと、元もとセッションに値"1"が格納されていたとしても、
2つのリクエストでそれぞれ+1されて"3"となると思いきや、"2"という値が格納されることになります。
R1の(A)で"1"を読み出し、R2の(A)でも"1"が読み出されるのです。
そして、R1の(C)で"2"を書き込み、R2の(C)でも"2"を書き込む…。
上記は簡単な例ですが、セッションに格納した情報をいじるというのは実は大変難しいことなのです。
対策
対策としては並列処理のプログラミングの知恵をそのまま生かすことができます。
セッションからの読み出しから書き込みまでをsynchronizedキーワードによって同期する方法で防ぐことができます。
しかし、同期すると同時にひとつの処理しかできないわけですから重たい処理をロックするとHTTPリクエストが
大渋滞を起こしてしまうことがあります。リロードボタンでDoSアタックができてしまうようでは困りますね。
ロックを行わない同期としてはコンペア・アンド・スワップなどの手法があります。
楽観排他などとイメージは似ていますね。書き込み段階で読み出した値と違っていないかを確認することで、
途中で書き換えられていた場合にエラーで落とすことが出来ます。
この先はもう並列処理の話になってしまうので本稿では割愛しますが、
セッションを扱う際には同期を意識する必要があることは心にとどめ置いてください。
投稿日時 : 2007年10月4日 13:53