request.getSession()をロックオブジェクトとして使用していますか?
-
10-07-2019 - |
質問
セッション属性を取得および設定するJavaコードがあります:
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
このコードをスレッドセーフにするために、同期ブロックにラップしたいと思います。しかし、ロックオブジェクトとして何を使用しますか?セッションを使用するのは理にかなっていますか?
synchronized (session) {
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
}
解決
一般に、ユーザーが制御できないロックを使用することは嫌われます。ロックはできるだけ厳密にスコープする必要があり、セッションは多かれ少なかれグローバルオブジェクトであるため、法案に適合しません。 java.util.concurrent.locks パッケージをクラスにスコープします。
他のヒント
サーブレットのコンテキストでは?サーブレットは複数のプロセスに分散できるため、常に同じセッションオブジェクトを使用できるとは限りません。その結果、サーブレットコンテナが同じプロセスで異なるセッションオブジェクトを提供することを決定する可能性があります。
IIRCのBrian Goetzは、セッションを正しく行うことの難しさに関する興味深い記事を書きました。
私のアドバイス:セッションはできるだけ避け、ランダムなオブジェクトをロックしないでください(他の目的のないロックオブジェクトを使用してください)。
あなたが投稿した記事を見てみました。すべての同期をスキップして、compare-and-setを使用してデータが正しいことを確認することで、作成者が行ったのと同じアプローチを取ることができます。
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<TYPE> holder
= (AtomicReference<TYPE>) ctx.getAttribute(TEST_ATTR);
while (true) {
TYPE oldVal = holder.get();
TYPE newVal = computeNewVal(oldVal);
if (holder.compareAndSet(oldVal, newVal))
break;
}
他のスレッドが<!> quot; holder <!> quotの値を更新した場合、holder.compareAndSet(old、new)はfalseを返します。最後に読んだので。 Holder.compareAndSet(、)はwhile(true)ループに入れられるため、書き込み前に値が変更された場合は、値を再度読み取り、書き込みを再試行することができます。
仕様は、これがまったく役立つことを保証していません:
synchronized (session) {
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
}
(特定の実装で機能する可能性がありますが、すべてのコンテナで機能するという保証はありません。)
Servlet 2.5 MR6のコメント:
リクエストスレッドを実行する複数のサーブレットは、同時に同じセッションオブジェクトにアクティブにアクセスできます。コンテナは、セッション属性を表す内部データ構造の操作がスレッドセーフな方法で実行されることを保証する必要があります。開発者には、属性オブジェクト自体へのスレッドセーフなアクセスに対する責任があります。これにより、HttpSessionオブジェクト内の属性コレクションが同時アクセスから保護され、アプリケーションがそのコレクションを破損させる機会がなくなります。
基本的に、仕様により問題になります。ソリューションは、アプリケーションの設計と展開計画に合わせて調整する必要があります。すべての場合に機能する問題のグローバルな解決策があるかどうかはわかりません。ただし、アプリケーションのアーキテクチャとアプリケーションサーバーの構成についてより具体的に考えれば、より良い答えが得られる可能性があります。
少なくとも2つの理由でコードが機能しません。
1)セッションが存在しない場合、同じユーザーに対して簡単に2回作成でき、createい競合状態になります。
2)セッションがスレッド間で同じオブジェクトでない場合、とにかく動作しません。セッションはおそらく別のスレッドの同じセッションへのequals()
になりますが、それは機能しません。
session.setAttribute()
はスレッドセーフであるため、ロックする必要はありません(上記の@McDowellのサーブレット仕様コメントを参照)。
ただし、別の例を使用してみましょう。属性の値をチェックし、<!> lt; = 100の場合に更新したいとしましょう。この場合、getAttribute()
the compare <!> lt; = 100のコードブロックを同期する必要があります。およびsetAttribute()
。
今、あなたはロックに何を使うべきですか?ロックに異なるオブジェクトが使用されている場合、同期は行われないことに注意してください。したがって、異なるコードブロックは同じオブジェクトを使用する必要があります。 session
オブジェクトの選択は問題ないかもしれません。ロックを取得した場合でも、別のコードブロックがセッションオブジェクトをロックしない限り、異なるコードブロックがセッションにアクセス(読み取り/書き込みの両方)する可能性があることにも注意してください。ここでの落とし穴は、コード内の多くの場所がセッションオブジェクトをロックするため、待機する必要があることです。たとえば、コードブロックがセッション属性Aを使用し、別のコードの塊がセッション属性Bを使用する場合、両方がセッションオブジェクトをロックすることで互いに待機する必要がないと便利です。 LockForAという名前の静的オブジェクトとLockForBの使用は、使用するコードにとってより良い選択かもしれません。 synchronized (LockForA) { }.
同じ問題が発生し、クラスにスコープを設定したくありませんでした。オブジェクトの作成には、オブジェクトがすでに作成されている他のセッションの2番目のスレッドが停止する可能性があるためです。リクエストのセッションオブジェクトを使用して同期したくないのは、アプリケーションサーバーの実装が異なるリクエストで異なるセッションファサードを返す可能性があり、異なるオブジェクトでの同期が機能しないためです。そこで、ロックとして使用するオブジェクトをセッションに配置し、ダブルチェックイディオムを使用して、ロックが1回だけ作成されるようにします。オブジェクトの作成は非常に高速であるため、MyObjectのスコープでの同期はもう問題ではありません。
Object lock = session.getAttribute("SessionLock");
if (lock == null) {
synchronized (MyObject.class) {
lock = session.getAttribute("SessionLock");
if(lock == null) {
lock = new Object();
session.setAttribute("SessionLock", lock);
}
}
}
synchronized (lock) {
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
}