Can a spring transaction synchronize a synchronized method?

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.

+4
source share
4 answers

The synchronized keyword requires, as you said, that the involved object is always the same. I myself have not noticed the above behavior, but your suspect may be correct.

Have you tried to deduce b from the doPost method? If this is every time, then there is some spring magic with AOP / cglib proxies.

Anyway, I would not rely on the syncronized keyword, but used something like ReentrantLock from java.util.concurrent.locks to make sure you synchronize, since your object b is always the same regardless of the possible multiple proxies cglib.

+2
source

The problem is that @Transactional encapsulates a synchronized method. Spring does this with AOP. The execution is something like this:

  • start a transaction
  • calling a method annotated with @Transactional
  • when the method returns a transaction commit

Steps 1 and 3. can be performed simultaneously by multiple threads. Therefore, you get multiple transaction starts.

The only solution is to synchronize the call with the method itself.

+4
source

Option 1:

 Delete synchronized of ServiceB and: public void doPost(Request request, Response response) { ... synchronized(this) { b.execute(parameters); } ... } 

Option 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); ... } 
0
source

Option 2:

Remove ServiceB Sync 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 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); ... } } 
0
source

Source: https://habr.com/ru/post/1300039/


All Articles