Spring事务可以取消同步同步方法吗?

发布于 2024-08-20 03:26:22 字数 1965 浏览 3 评论 0原文

我和我的同事有一个 Web 应用程序,它在 MyEclipse 内的 Tomcat 上使用 Spring 3.0.0 和 JPA (hibernate 3.5.0-Beta2)。其中一种数据结构是树。只是为了好玩,我们尝试使用 JMeter 对“插入节点”操作进行压力测试,并发现了并发问题。 Hibernate 报告发现两个具有相同私钥的实体,紧接着出现这样的警告:

 WARN  [org.hibernate.engine.loading.LoadContexts] fail-safe cleanup (collections) : ...

如果多个线程同时调用 insert() 方法,很容易看出如何发生此类问题。

我的 servlet A 调用服务层对象 B.execute(),然后服务层对象调用较低层对象 C.insert()。 (真正的代码太大,无法发布,因此有所删节。)

Servlet 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() 同步。已经是@Transactional了,但实际上需要同步的是业务逻辑,而不仅仅是持久化,所以这看起来很合理。

我的 C.insert() 方法也是 @Transactional。但由于 Spring 中的默认事务传播似乎是必需的,因此我认为没有为 C.insert() 创建任何新事务。

所有组件 A、B 和 C 都是 spring-bean,因此都是单例。如果确实只有一个 B 对象,那么我得出的结论是,不可能同时有多个威胁执行 b.execute() 。当负载较轻时,只使用单线程,就是这种情况。但是在负载下,会涉及其他线程,并且我看到在第一个线程打印“完成”之前有几个线程打印“开始”。这似乎违反了该方法的同步性质。

我决定在日志消息中打印 this 来确认是否只有一个 B 对象。所有日志消息都显示相同的对象 ID。

经过大量令人沮丧的调查后,我发现删除 B.execute() 的 @Transactional 可以解决问题。这条线消失后,我可以拥有很多线程,但我总是看到一个“开始”,然后是“结束”,然后再下一个“开始”(并且我的数据结构保持不变)。不知何故,synchronized 似乎仅在 @Transactional 不存在时才起作用。但我不明白为什么。有人可以帮忙吗?关于如何进一步研究这个问题有什么建议吗?

在堆栈跟踪中,我可以看到在 A.doPost() 和 B.execute() 之间以及 B.execute() 和 C.insert() 之间生成了 aop/cglib 代理。我想知道代理的构造是否会以某种方式破坏同步行为。

My colleague and I have a web application that uses Spring 3.0.0 and JPA (hibernate 3.5.0-Beta2) on Tomcat inside MyEclipse. One of the data structures is a tree. Just for fun, we tried stress-testing the "insert node" operation with JMeter, and found a concurrency problem. Hibernate reports finding two entities with the same private key, just after a warning like this:

 WARN  [org.hibernate.engine.loading.LoadContexts] fail-safe cleanup (collections) : ...

It is quite easy to see how such problems might occur if multiple threads call the insert() method concurrently.

My servlet A calls a service layer object B.execute(), which then calls a lower layer object C.insert(). (The real code is too big to post, so this is somewhat abridged.)

Servlet A:

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

Service 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);
  }

Sub-service C:

  @Transactional
  public void insert(params) {
    ...
    // data structure manipulation operations that should not be 
    // simultaneous with any other manipulation operations called by B.
    ...
  }

All my state-change calls go through B, so I decided to make B.execute() synchronized. It was already @Transactional, but it's actually the business logic that needs to be synchronized, not just the persistence, so that seems reasonable.

My C.insert() method was also @Transactional. But since the default transaction propagation in Spring seems to be Required, I don't think there was any new transaction being created for C.insert().

All components A, B, and C are spring-beans, and hence singletons. If there really is only one B object, then I conclude that it shouldn't be possible for more than one threat to execute b.execute() at a time. When the load is light, only a single thread is used, and this is the case. But under load, additional threads get involved, and I see several threads print "starting" before the first one prints "finishing". This seems to be a violation of the synchronized nature of the method.

I decided to print the this in the log messages to confirm whether there was only a single B object. All the log messages do show the same object id.

After much frustrating investigation, I discovered that removing the @Transactional for B.execute() solves the problem. With that line gone, I can have lots of threads, but I always see a "starting" followed by a "finishing" before the next "starting" (and my data structures stay intact). Somehow, the synchronized only seems to work when the @Transactional is not present. But I don't understand why. Can anyone help? Any tips as to how to look into this further?

In stack traces, I can see that there's an aop/cglib proxy generated in between A.doPost() and B.execute() - and also between B.execute() and C.insert(). I wonder whether somehow the proxy's construction might ruin the synchronized behaviour.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

仲春光 2024-08-27 03:26:22

问题在于@Transactional封装了synchronized方法。 Spring 使用 AOP 来实现这一点。
执行过程是这样的:

  1. 启动事务
  2. 当方法返回时调用带有 @Transactional 注释的方法
  3. 提交事务

步骤 1. 和 3. 可以由多个线程同时执行。因此,您可以多次开始交易。

您唯一的解决方案是同步对方法本身的调用。

The problem is that @Transactional encapsulates the synchronized method. Spring does this using AOP.
The execution goes something like this:

  1. start transaction
  2. call the method annotated with @Transactional
  3. when method returns commit the transaction

Steps 1. and 3. can be executed by many threads at the same time. Hence you get multiple starts of transaction.

Your only solution is to synchronize the call to the method itself.

不一样的天空 2024-08-27 03:26:22

正如您所说,同步关键字要求涉及的对象始终相同。我自己没有观察到上述行为,但你的怀疑可能是正确的。

您是否尝试过从 doPost 方法中注销 b ?如果每次都不同,那么 AOP/cglib 代理就会发挥一些 Spring 魔力。

无论如何,我不会依赖 Synchronized 关键字,而是使用类似 ReentrantLock 来自 java.util.concurrent.locks 以确保同步行为,因为无论可能有多个 cglib 代理,您的 b 对象始终相同。

Synchronized keyword requires, as you stated, that the object involved is always the same. I haven't observed the above mentioned behaviour myself but your suspect might just be correct one.

Have you tried logging out b from doPost -method? If that is different every time, then there is some spring magic with AOP/cglib proxies going on.

Anyway, I wouldn't rely on the syncronized keyword but use something like ReentrantLock from java.util.concurrent.locks to ensure the synchronization behavior instead, as your b object is always same regardless of possible multiple cglib proxies.

再浓的妆也掩不了殇 2024-08-27 03:26:22

选项 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);
    ...
}

Option 1:

Delete synchronized of ServiceB and:

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

Option 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);
    ...
}
难理解 2024-08-27 03:26:22

选项 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);
    ...
   }    
}

Option 2 Again:

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 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);
    ...
   }    
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文