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 announces a search for two objects with the same private key immediately after a warning:
WARN [org.hibernate.engine.loading.LoadContexts] fail-safe cleanup (collections) : ...
It is fairly easy to understand how such problems can occur if several threads call the insert () method at the same time.
My servlet A calls the B.execute () service layer object, which then calls the C.insert () lower layer object. (This code is too large to publish, so this has been reduced a bit.)
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 in fact, the business logic should be synchronized, not just persistence, so this seems reasonable.
My C.insert () method was also @Transactional . But since distributing default transactions in Spring seems mandatory, I don't think a new transaction has been created for C.incert ().
All components A, B, and C are spring - beans, which means singletones. If there really is only one B object, I conclude that it cannot be possible for more than one threat to execute b.execute () at a time. When the load is light, only one stream is used, and it is. But under the load, additional threads are involved, and I see that several threads print “start” before the first prints “finish”. This, apparently, is a violation of the nature of the synchronized method.
I decided to print this in the log messages to confirm if only one object B exists. All log messages show the same object identifier.
After much investigation, I found that removing @Transactional for B.execute () solves the problem. I can have many threads with this line, but I always see a “start”, followed by a “finish” before the next “start” (and my data structures remain untouched). Be that as it may, synchronized only works when @Transactional not. But I do not understand why. Can anyone help? Any tips on how to look further?
In the stack trace, I see that there is aop / cglib proxy created between A.doPost () and B.execute (), as well as between B.execute () and C.insert (). I wonder if some kind of proxy design could ruin synchronized behavior.