我的同事和我有一个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
行为。
答案 0 :(得分:5)
问题是@Transactional封装了synchronized方法。 Spring使用AOP做到这一点。 执行类似于:
步骤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);
...
}
}