2012-03-21 4 views
25

ОБНОВЛЕНИЕ: Решение сразу после вопроса.Возможно ли синхронизация внутри HttpSession?

Вопрос:

Обычно синхронизация сериализации параллельных запросов в пределах виртуальной машины Java, например

private static final Object LOCK = new Object(); 

public void doSomething() { 
    ... 
    synchronized(LOCK) { 
    ... 
    } 
    ... 
} 

При просмотре веб-приложений, некоторые синхронизации на «JVM глобальной» Прицел может быть, становится узким местом производительности и синхронизации только в пределах HttpSession бы больше смысла пользователя.

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

HttpSession session = getHttpServletRequest().getSession(); 
synchronized (session) { 
    ... 
} 

Ключевой вопрос:
ли он гарантировал, что объект сеанса тот же экземпляр для всех потоков обработки запросов от одного пользователя?

Обобщенный ответ/решение:

Кажется, что сам объект сеанса не всегда такой же, как в зависимости от реализации контейнера сервлетов (Tomcat, GlassFish, ...) и метод getSession() может возвращать только экземпляр обертки.

Поэтому рекомендуется использовать пользовательскую переменную, хранящуюся в сеансе, которая будет использоваться в качестве объекта блокировки.

Вот мой код предложения, обратная связь приветствуется:

где-то в классе Helper, например, MyHelper:

private static final Object LOCK = new Object(); 

public static Object getSessionLock(HttpServletRequest request, String lockName) { 
    if (lockName == null) lockName = "SESSION_LOCK"; 
    Object result = request.getSession().getAttribute(lockName); 
    if (result == null) { 
     // only if there is no session-lock object in the session we apply the global lock 
     synchronized (LOCK) { 
      // as it can be that another thread has updated the session-lock object in the meantime, we have to read it again from the session and create it only if it is not there yet! 
      result = request.getSession().getAttribute(lockName); 
      if (result == null) { 
       result = new Object(); 
       request.getSession().setAttribute(lockName, result); 
      } 
     } 
    } 
    return result; 
} 

, а затем вы можете использовать его:

Object sessionLock = MyHelper.getSessionLock(getRequest(), null); 
synchronized (sessionLock) { 
    ... 
} 

Любые комментарии по этому решению?

+0

Возможный дубликат [Является ли потоком HttpSession безопасным, установлены/получают атрибуты потока атрибутов потока?] (Http://stackoverflow.com/questions/616601/is-httpsession-thread-safe-are-set-get-attribute -thread-safe-operations) –

+1

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

+1

@SahilMuthoo: Я не согласен, OP спрашивает, какой объект безопасен для синхронизации/блокировки HttpSession, а не является ли HttpSession «потокобезопасным» или нет. –

ответ

22

Я нашел хорошее объяснение в JavaDoc для WebUtils.getSessionMutex():

Во многих случаях HttpSession ссылаться сам является безопасным мьютексом, а также, так как он всегда будет такой же, ссылкой на объект для того же активный логический сеанс. Однако, это не гарантируется в разных контейнерах сервлетов; единственный 100% безопасный способ - это мьютексы сеанса.

Этот метод используется в качестве замка, когда synchronizeOnSession флаг установлен:

Object mutex = WebUtils.getSessionMutex(session); 
synchronized (mutex) { 
    return handleRequestInternal(request, response); 
} 

Если посмотреть на реализацию getSessionMutex(), он фактически использует некоторый атрибут пользовательского сеанса, если присутствует (под org.springframework.web.util.WebUtils.MUTEX ключ) или HttpSession экземпляр, если нет:

Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE); 
if (mutex == null) { 
    mutex = session; 
} 
return mutex; 

Вернуться к простой сервлет спецификации - быть атрибут 100% уверен, что использование пользовательского сеанса, а не HttpSession объект сам.

Смотрите также

+0

Это полезно, только если вы используете платформу Spring Core? – basZero

+1

@basZero: нет, я показал примеры кода из источника Spring MVC, чтобы дать вам пример того, как он реализован в зрелой веб-среде. Вы можете основывать свое собственное решение на этих простых концепциях. –

+0

@Tomasz_Nurkiewicz ok, обновил мой вопрос с помощью специального решения. – basZero

2

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

Итак, что ваш вопрос вызывает интересный вопрос: является ли объект HttpSession двумя отдельными веб-вызовами из одного и того же сеанса в качестве одной и той же ссылки на объекты в веб-контейнере, или они представляют собой два объекта, которые просто имеют сходные данные в них? Я нашел this интересную дискуссию о веб-приложениях с поддержкой состояния, в которых несколько обсуждается HttpSession. Кроме того, в CodeRanch есть this discussion о безопасности потоков в HttpSession.

Из этих обсуждений кажется, что HttpSession действительно является одним и тем же объектом. Один простой тест - написать простой сервлет, посмотреть HttpServletRequest.getSession() и посмотреть, ссылается ли он на тот же объект сеанса на несколько вызовов. Если это так, то я думаю, что ваша теория звучит, и вы можете использовать ее для синхронизации между пользовательскими вызовами.

+0

Большое спасибо за [первый указатель] (http://www.ibm.com/developerworks/library/j-jtp09238/index.html), автор Брайан Гетц, во всяком случае, Гуру, когда дело касается параллелизма. Начиная с его написания, кажется, что нормально синхронизировать объект сеанса: _Сертификация запросов на HttpSession делает много опасностей параллелизма уйти. – basZero

+0

. Есть две проблемы: 1. Спецификация Servlet не говорит о том, что возврат Значение getSession всегда должно быть одним и тем же объектом. 2. Если у вас есть фильтр (от сторонней библиотеки lib или иначе), который обертывает объект Request, который также может каждый раз обматывать возвращаемое значение getSession новой оболочкой. Короче говоря, если нет гарантий, это небезопасно. – megaflop

+0

Мужчина, @daiscog, рассказывай о взрыве из прошлого ... это было от 3-х лет назад! Во всяком случае, я согласен с вами в том, что вы бы выговорили моего молодого Я, чтобы не думать обо всех сторонах вопроса. – CodeChimp

1

Ответы верны. Если вы хотите, чтобы один и тот же пользователь выполнял одновременно два разных (или одинаковых) запроса, вы можете синхронизировать их с HttpSession. Лучше всего это использовать фильтр.

Примечание:

  • если ваши ресурсы (изображения, скрипты и любая нединамическая файл) также приходит через сервлет, вы можете создать узкое место. Тогда убедитесь, что синхронизация выполняется только на динамических страницах.
  • Попытайтесь избежать getSession напрямую, вам лучше проверить, существует ли сеанс, потому что сеанс не создается автоматически для гостей (так как ничего не нужно хранить в сеансе). Затем, если вы вызываете getSession(), сеанс будет создан, и память будет потеряна. Затем используйте getSession(false) и попытайтесь разобраться с результатом null, если сеанс уже не существует (в этом случае не синхронизируйтесь).
+1

Если вы выполняете синхронизацию внутри фильтра, вы фактически отключите параллельную обработку запросов для того же сеанса. Это может быть или не быть тем, что вы хотите, но это звучит как слишком широкая синхронизация со мной. Всегда нужно стремиться к синхронизации на максимально возможной площади. –

+0

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

7

В целом, не полагайтесь на HttpServletRequest.getSession(), возвращая тот же самый объект. Для сервлет-фильтров легко создать оболочку вокруг сеанса по любой причине. Ваш код будет видеть только эту оболочку, и это будет другой объект для каждого запроса. Поместите некоторую общую блокировку в сам сеанс. (Слишком плохо нет putIfAbsent).

+0

Очень хорошая точка в отношении оберток. Идея поместить объект в сеанс очень хороша. Вы уже использовали это? – basZero

+0

Я использовал комбинированную технику ... заблокировал объект, хранящийся в сеансе, и синхронизировал сам сеанс, чтобы поместить в него этот объект блокировки :-) –

+0

Привет @ PeterŠtibraný, что-то вроде моего обобщенного ответа выше? – basZero

0

Решение весеннего каркаса, упомянутое Томашем Нуркевичем, случайно исправлено в кластерных средах только потому, что спецификация сервлета требует согласованности сеанса на нескольких JVM.В противном случае он не делает магии самостоятельно для сценариев, где несколько запросов распространяются на разных машинах. См. Обсуждение в this thread, которое проливает некоторый свет на объект.

2

Как уже говорилось, сеансы могут быть обернуты контейнерами сервлетов, и это порождает проблему: hashCode сессии() отличается от запросов, т. Е. Они не являются одним и тем же экземпляром и, следовательно, не могут быть синхронизированы! Многие контейнеры позволяют продолжать сеанс. В этом случае в определенное время, когда сеанс истек, он сохраняется на диске. Даже когда сеанс извлекается путем десериализации, он не является тем же объектом, что и раньше, поскольку он не имеет одинакового адреса памяти, например, когда он был в памяти до процесса сериализации. Когда сеанс загружается с диска, он помещается в память для дальнейшего доступа, пока не будет достигнут «maxInactiveInterval» (истекает). Подведение итогов: сессия может быть не одинаковой между многими веб-запросами! Это будет то же самое, что и в памяти. Даже если вы поместите атрибут в сеанс для совместного использования блокировки, он не будет работать, потому что он также будет сериализован в фазе сохранения.

+0

Итак, вы говорите, что мое документированное решение не сработает? Я думал, что синхронизация на объекте в сеансе (атрибуте) будет работать. – basZero

+0

Вы проверили это? –

+0

Нет, еще нет, я ищу правильное решение, прежде чем приступать к внедрению. – basZero

0

Использование

private static final Object LOCK = new Object(); 

Вы используете один и тот же замок для всех сеансов, и это было основной причиной тупика я сделал лицо. Итак, каждая сессия в вашей реализации имеет такое же состояние гонки, что и плохо.

Необходимо изменить.

Другой предлагаемый ответ:

Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE); 
if (mutex == null) { 
    mutex = session; 
} 
return mutex; 

кажется намного лучше.

+1

Спасибо за ваш ответ, но посмотрите на мое обобщенное решение наверху. Объект 'LOCK' используется только тогда, когда создан сеанс, который требуется для синхронизации в глобальном масштабе, чтобы избежать запросов на создание объектов в сеансе. – basZero

+0

У меня возникли взаимоблокировки на моем webapp numbeo.com, когда вы пытаетесь синхронизировать глобально, когда создается атрибут сеанса.Основная причина может заключаться в том, что некоторые методы запроса, сеанса и ответа могут потребовать время в оба конца, которое может увеличиться до 1 с. –

+1

Синхронизация по всему миру является допустимым прецедентом. ваши тупики могут исходить из вашей реализации, как вы их используете. – basZero

1

Другое решение предложено в "Murach-х Java сервлеты и JSP (третье издание)" Книга:

Cart cart; 
final Object lock = request.getSession().getId().intern(); 
synchronized (lock) { 
    cart = (Cart) session.getAttribute("cart"); 
} 
+2

интересная идея, однако это сериализует ВСЕ запросы пользователя, в то время как мое предлагаемое решение может обрабатывать различные зависящие от пользователя блокировки, чтобы решать вам сериализацию пользовательских запросов. – basZero

1

Лично я реализую сеанс блокировки с помощью в HttpSessionListener *:

package com.example; 

@WebListener 
public final class SessionMutex implements HttpSessionListener { 
    /** 
    * HttpSession attribute name for the session mutex object. The target for 
    * this attribute in an HttpSession should never be altered after creation! 
    */ 
    private static final String SESSION_MUTEX = "com.example.SessionMutex.SESSION_MUTEX"; 

    public static Object getMutex(HttpSession session) { 
     // NOTE: We cannot create the mutex object if it is absent from 
     // the session in this method without locking on a global 
     // constant, as two concurrent calls to this method may then 
     // return two different objects! 
     // 
     // To avoid having to lock on a global even just once, the mutex 
     // object is instead created when the session is created in the 
     // sessionCreated method, below. 

     Object mutex = session.getAttribute(SESSION_MUTEX); 

     // A paranoia check here to ensure we never return a non-null 
     // value. Theoretically, SESSION_MUTEX should always be set, 
     // but some evil external code might unset it: 
     if (mutex == null) { 
      // sync on a constant to protect against concurrent calls to 
      // this method 
      synchronized (SESSION_MUTEX) { 
       // mutex might have since been set in another thread 
       // whilst this one was waiting for sync on SESSION_MUTEX 
       // so double-check it is still null: 
       mutex = session.getAttribute(SESSION_MUTEX); 
       if (mutex == null) { 
        mutex = new Object(); 
        session.setAttribute(SESSION_MUTEX, mutex); 
       } 
      } 
     } 
     return mutex; 
    } 

    @Override 
    public void sessionCreated(HttpSessionEvent hse) { 
     hse.getSession().setAttribute(SESSION_MUTEX, new Object()); 
    } 

    @Override 
    public void sessionDestroyed(HttpSessionEvent hse) { 
     // no-op 
    } 
} 

Когда мне нужен сеанс семафор, я могу затем использовать:

synchronized (SessionMutex.getMutex(request.getSession())) { 
    // ... 
} 

__

* FWIW Мне очень нравится решение, предложенное в самом вопросе, поскольку оно предусматривает именованные блокировки сеанса, так что запросы на независимые ресурсы не должны использовать одну и ту же блокировку сеанса. Но если один сеанс блокировки - это то, что вы хотите, тогда этот ответ может быть прямо на вашей улице.

Смежные вопросы