我的同事和我有一个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都是弹簧豆,因此是单体.如果确实只有一个B对象,那么我得出结论,一次执行b.execute()不应该有多个威胁.当负载很轻时,只使用一个线程,情况就是这样.但是在负载下,其他线程也会被涉及,我看到几个线程在第一个打印“完成”之前打印“开始”.这似乎违反了该方法的同步性质.
我决定在日志消息中打印它以确认是否只有一个B对象.所有日志消息都显示相同的对象ID.
在经历了令人沮丧的调查后,我发现删除@Transactional for B.execute()可以解决问题.随着那条线的消失,我可以拥有很多线程,但我总是会在下一个“开始”之前看到一个“开始”,然后是“完成”(我的数据结构保持不变).不知何故,只有当@Transactional不存在时,似乎才有效.但我不明白为什么.有人可以帮忙吗?关于如何进一步研究这个问题的任何提示?
在堆栈跟踪中,我可以看到在A.doPost()和B.execute()之间生成了一个aop / cglib代理 – 以及B.execute()和C.insert()之间.我想知道代理的构造是否会破坏同步行为.
最佳答案:
正如您所说,同步关键字要求所涉及的对象始终相同.我自己没有观察到上述行为,但你的嫌疑人可能是正确的.
你有没有尝试从doPost -method注销b?如果每次都不同,那么AOP / cglib代理就会有一些神奇的魔力.
无论如何,我不会依赖同步关键字,而是使用java.util.concurrent.locks中的ReentrantLock来确保同步行为,因为无论可能的多个cglib代理如何,b对象总是相同的.
【本文转自:国外高防服务器 http://www.558idc.com/usa.html转载请说明出处】