Могут ли весенние транзакции не синхронизироваться с синхронизированным методом? - PullRequest
5 голосов
/ 02 февраля 2010

У меня и моего коллеги есть веб-приложение, которое использует Spring 3.0.0 и JPA (hibernate 3.5.0-Beta2) на Tomcat внутри MyEclipse. Одной из структур данных является дерево. Ради интереса мы попробовали провести стресс-тестирование операции «вставка узла» с JMeter и обнаружили проблему с параллелизмом. Hibernate сообщает об обнаружении двух объектов с одним и тем же закрытым ключом сразу после предупреждения, подобного этому:

 WARN  [org.hibernate.engine.loading.LoadContexts] fail-safe cleanup (collections) : ...

Довольно легко увидеть, как могут возникнуть такие проблемы, если несколько потоков вызывают метод insert () одновременно.

Мой сервлет A вызывает объект уровня обслуживания B.execute (), который затем вызывает объект уровня C.insert (). (Реальный код слишком велик для публикации, поэтому он несколько сокращен.)

Сервлет 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. Все сообщения журнала показывают один и тот же идентификатор объекта.

После долгих разочарований я обнаружил, что удаление @Transactional для B.execute () решает проблему. С этой строкой у меня может быть много потоков, но я всегда вижу «начало» с последующим «окончанием» перед следующим «началом» (и мои структуры данных остаются неизменными). Каким-то образом synchronized, кажется, работает только тогда, когда @Transactional отсутствует. Но я не понимаю почему. Кто-нибудь может помочь? Любые советы о том, как смотреть в это дальше?

В трассировке стека я вижу, что прокси-сервер aop / cglib генерируется между A.doPost () и B.execute (), а также между B.execute () и C.insert (). Интересно, может ли конструкция прокси испортить поведение synchronized.

Ответы [ 4 ]

5 голосов
/ 15 июля 2014

Проблема в том, что @Transactional инкапсулирует синхронизированный метод. Spring делает это с помощью АОП. Выполнение происходит примерно так:

  1. начать транзакцию
  2. вызвать метод, помеченный @ Transactional
  3. когда метод возвращает фиксацию транзакции

Шаги 1. и 3. могут выполняться несколькими потоками одновременно. Следовательно, вы получаете несколько стартов транзакции.

Ваше единственное решение - синхронизировать вызов с самим методом.

2 голосов
/ 02 февраля 2010

Синхронизированное ключевое слово требует, как вы заявили, что объект всегда один и тот же. Я не наблюдал вышеупомянутое поведение сам, но ваш подозреваемый мог бы быть только правильным.

Вы пытались выйти из метода doPost? Если каждый раз по-разному, тогда происходит какая-то весенняя магия с прокси AOP / cglib.

В любом случае, я бы не стал полагаться на ключевое слово syncronized, а использовал бы что-то вроде ReentrantLock из java.util.concurrent.locks, чтобы обеспечить синхронизацию, поскольку ваш объект b всегда одинаков, независимо от возможного несколько прокси-серверов cglib.

0 голосов
/ 05 мая 2011

Вариант 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);
    ...
   }    
}
0 голосов
/ 20 апреля 2011

Вариант 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);
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...