Spring事务可以使同步方法不同步吗?

时间:2010-02-02 11:06:04

标签: hibernate spring jpa transactions synchronized

我的同事和我有一个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()synchronized。它已经是@Transactional,但它实际上是需要同步的业务逻辑,而不仅仅是持久性,所以这似乎是合理的。

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

所有组分A,B和C都是弹簧豆,因此是单体。如果确实只有一个B对象,那么我得出结论,一次执行b.execute()不应该有多个威胁。当负载很轻时,只使用一个线程,情况就是这样。但是在负载下,其他线程也会被涉及,我看到几个线程在第一个打印“完成”之前打印“开始”。这似乎违反了该方法的synchronized性质。

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

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

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

4 个答案:

答案 0 :(得分:5)

问题是@Transactional封装了synchronized方法。 Spring使用AOP做到这一点。 执行类似于:

  1. 启动交易
  2. 调用@Transactional
  3. 注释的方法
  4. 当方法返回提交事务时
  5. 步骤1.和3.可以由多个线程同时执行。因此,您可以获得多次交易。

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

答案 1 :(得分:2)

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

您是否尝试从doPost -method注销b?如果每次都不同,那么AOP / cglib代理就会有一些神奇的魔力。

无论如何,我不会依赖同步关键字,而是使用java.util.concurrent.locks中的ReentrantLock来确保同步行为,因为无论多少cglib,你的b对象总是相同的代理。

答案 2 :(得分: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);
    ...
}

答案 3 :(得分: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);
    ...
   }    
}