5

私の同僚と私は、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これはメソッドの性質に違反しているようです。

thisB オブジェクトが 1 つしかないかどうかを確認するために、ログ メッセージに を出力することにしました。すべてのログ メッセージは、同じオブジェクト ID を示しています。

非常にイライラする調査の結果、@Transactionalfor B.execute() を削除すると問題が解決することがわかりました。その行がなくなると、多くのスレッドを作成できますが、次の「開始」の前に「開始」に続いて「終了」が常に表示されます (データ構造はそのまま残ります)。どういうわけか、が存在しないsynchronized場合にのみ機能するよう@Transactionalです。しかし、私はその理由を理解していません。誰でも助けることができますか?これをさらに調べる方法に関するヒントはありますか?

スタック トレースでは、A.doPost() と B.execute() の間、および B.execute() と C.insert() の間に aop/cglib プロキシが生成されていることがわかります。どういうわけか、プロキシの構築がsynchronized動作を台無しにするのではないかと思います。

4

4 に答える 4

5

問題は、@Transactional が同期メソッドをカプセル化することです。Spring は AOP を使用してこれを行います。実行は次のようになります。

  1. 取引開始
  2. @Transactional アノテーションが付けられたメソッドを呼び出す
  3. メソッドがトランザクションをコミットするとき

手順 1. と 3. は、多数のスレッドで同時に実行できます。したがって、トランザクションの複数の開始を取得します。

唯一の解決策は、メソッド自体への呼び出しを同期することです。

于 2014-07-15T14:37:02.287 に答える
2

Synchronized キーワードは、あなたが述べたように、関連するオブジェクトが常に同じであることを必要とします。私は上記の動作を自分で観察していませんが、あなたの容疑者は正しいものかもしれません.

doPost メソッドから b をログアウトしようとしましたか? それが毎回異なる場合は、AOP/cglib プロキシが進行中のスプリング マジックがあります。

とにかく、syncronized キーワードに依存するのではなく、java.util.concurrent.locks のReentrantLockのようなものを使用して同期動作を保証します。複数の cglib プロキシの可能性に関係なく、b オブジェクトは常に同じであるためです。

于 2010-02-02T11:29:03.643 に答える
0

オプション2再び:

ServiceBの同期を削除します。

public class ProxyServiceB (extends o implements) ServiceB
{
    private ServiceB serviceB;
    public ProxyServiceB(ServiceB serviceB)
    {
        this.serviceB =serviceB;
    }
    public synchronized void execute(parameters) 
    {
         this.serviceB.execute(parameters);
    }
} 

public class TheServlet extends HttpServlet
{
   private static ProxyServiceB proxyServiceB = null;

   private static ProxyServiceB getProxyServiceBInstance()
   {
        if(proxyServiceB == null)
        {
            return proxyServiceB = new ProxyServiceB(b);
        }
        return proxyServiceB;
   }

   public void doPost(Request request, Response response) 
   {
    ...
     ProxyServiceB proxyServiceB = getProxyServiceBInstance();
    proxyServiceB .execute(parameters);
    ...
   }    
}
于 2011-05-04T23:48:00.543 に答える
0

オプション1:

Delete synchronized of ServiceB and:

public void doPost(Request request, Response response) {
    ...
    synchronized(this)
    {
        b.execute(parameters);
    }
    ...
  }

オプション 2:

Delete synchronized of ServiceB and:

public class ProxyServiceB (extends o implements) ServiceB
{
    private ServiceB serviceB;
    public ProxyServiceB(ServiceB serviceB)
    {
        this.serviceB =serviceB;
    }
    public synchronized void execute(parameters) 
    {
         this.serviceB.execute(parameters);
    }
} 

public void doPost(Request request, Response response) 
{
    ...
    ProxyServiceB proxyServiceB = new ProxyServiceB(b);
    proxyServiceB .execute(parameters);
    ...
}
于 2011-04-20T12:29:15.563 に答える