私の同僚と私は、MyEclipse 内の Tomcat で Spring 3.0.0 と JPA (hibernate 3.5.0-Beta2) を使用する Web アプリケーションを持っています。データ構造の 1 つがツリーです。ふざけて、JMeter で「ノードの挿入」操作のストレス テストを試みたところ、同時実行性の問題が見つかりました。Hibernate は、次のような警告の直後に、同じ秘密鍵を持つ 2 つのエンティティを見つけたことを報告します。
WARN [org.hibernate.engine.loading.LoadContexts] fail-safe cleanup (collections) : ...
複数のスレッドが同時に insert() メソッドを呼び出すと、このような問題がどのように発生するかを簡単に確認できます。
私のサーブレット A は、サービス層オブジェクト B.execute() を呼び出し、それが下位層オブジェクト C.insert() を呼び出します。(実際のコードは掲載するには大きすぎるため、多少簡略化しています。)
サーブレット A:
public void doPost(Request request, Response response) {
...
b.execute(parameters);
...
}
サービス B:
@Transactional //** Delete this line to fix the problem.
public synchronized void execute(parameters) {
log("b.execute() starting. This="+this);
...
c.insert(params);
...
log("b.execute() finishing. This="+this);
}
サブサービス C:
@Transactional
public void insert(params) {
...
// data structure manipulation operations that should not be
// simultaneous with any other manipulation operations called by B.
...
}
すべての状態変更呼び出しは B を経由するため、 B.execute() を作成することにしましたsynchronized
。それはすでに でしたが@Transactional
、実際には永続性だけでなく、同期する必要があるのはビジネス ロジックであるため、それは合理的に思えます。
私の C.insert() メソッドも@Transactional
. しかし、Spring のデフォルトのトランザクションの伝播は Required のようですので、C.insert() 用に作成された新しいトランザクションはなかったと思います。
コンポーネント A、B、および C はすべて spring-bean であるため、シングルトンです。実際に B オブジェクトが 1 つしかない場合、一度に複数の脅威が b.execute() を実行することはできないはずだと結論付けます。負荷が軽いときはシングルスレッドしか使用されず、これが当てはまります。しかし、負荷がかかると、追加のスレッドが関与し、最初のスレッドが「終了」を出力する前に、いくつかのスレッドが「開始」を出力します。synchronized
これはメソッドの性質に違反しているようです。
this
B オブジェクトが 1 つしかないかどうかを確認するために、ログ メッセージに を出力することにしました。すべてのログ メッセージは、同じオブジェクト ID を示しています。
非常にイライラする調査の結果、@Transactional
for B.execute() を削除すると問題が解決することがわかりました。その行がなくなると、多くのスレッドを作成できますが、次の「開始」の前に「開始」に続いて「終了」が常に表示されます (データ構造はそのまま残ります)。どういうわけか、が存在しないsynchronized
場合にのみ機能するよう@Transactional
です。しかし、私はその理由を理解していません。誰でも助けることができますか?これをさらに調べる方法に関するヒントはありますか?
スタック トレースでは、A.doPost() と B.execute() の間、および B.execute() と C.insert() の間に aop/cglib プロキシが生成されていることがわかります。どういうわけか、プロキシの構築がsynchronized
動作を台無しにするのではないかと思います。