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

Joh*_*ohn 5 spring hibernate 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) : ...
Run Code Online (Sandbox Code Playgroud)

如果多个线程同时调用insert()方法,很容易看出这些问题可能会发生.

我的servlet A调用服务层对象B.execute(),然后调用较低层对象C.insert().(真正的代码太大了,不能发布,所以这有点删节.)

Servlet A:

  public void doPost(Request request, Response response) {
    ...
    b.execute(parameters);
    ...
  }
Run Code Online (Sandbox Code Playgroud)

服务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);
  }
Run Code Online (Sandbox Code Playgroud)

子服务C:

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

我所有的状态更改调用都通过B,所以我决定创建B.execute()synchronized.它已经是@Transactional,但它实际上是需要同步的业务逻辑,而不仅仅是持久性,所以这似乎是合理的.

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

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

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

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

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

Dus*_*czh 5

问题是@Transactional封装了synchronized方法.Spring使用AOP做到这一点.执行是这样的:

  1. 开始交易
  2. 调用@Transactional注释的方法
  3. 当方法返回时提交事务

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

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


plo*_*ouh 2

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

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

无论如何,我不会依赖syncronized关键字,而是使用java.util.concurrent.locks中的ReentrantLock之类的东西来确保同步行为,因为无论可能有多个cglib代理,您的b对象总是相同的。