Использование request.getSession () в качестве объекта блокировки? - PullRequest
11 голосов
/ 30 ноября 2009

У меня есть некоторый код Java, который получает и устанавливает атрибут сеанса:

Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
  obj = new MyObject();
  session.setAttribute(obj);
}

Чтобы сделать этот код потокобезопасным, я бы хотел обернуть его в синхронизированный блок. Но что я использую в качестве объекта блокировки? Имеет ли смысл использовать сеанс?

synchronized (session) {
  Object obj = session.getAttribute(TEST_ATTR);
  if (obj==null) {
    obj = new MyObject();
    session.setAttribute(obj);
  }
}

Ответы [ 7 ]

5 голосов
/ 30 ноября 2009

Обычно не одобряют использование замка, который вы не можете контролировать. Блокировка должна быть ограничена настолько, насколько это возможно, и поскольку сеанс является более или менее глобальным объектом, он не соответствует требованиям. Попробуйте использовать отдельную блокировку из пакета java.util.concurrent.locks и поместите ее в свой класс.

3 голосов
/ 30 ноября 2009

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

ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<TYPE> holder 
    = (AtomicReference<TYPE>) ctx.getAttribute(TEST_ATTR);
while (true) {
    TYPE oldVal = holder.get();
    TYPE newVal = computeNewVal(oldVal);
    if (holder.compareAndSet(oldVal, newVal))
        break;
} 

holder.compareAndSet (old, new) вернет false, если какой-то другой поток обновил значение «holder» с момента вашего последнего прочтения. holder.compareAndSet (,) помещается в цикл while (true), так что если значение изменилось до того, как вы смогли его записать, то у вас есть возможность снова прочитать значение и повторить попытку записи.

http://java.sun.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicReference.html

3 голосов
/ 30 ноября 2009

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

IIRC, Брайан Гетц (Brian Goetz) написал интересную статью о сложности правильного ведения сессий.

Мой совет: держитесь подальше от сессий, насколько это возможно, и не блокируйте случайные объекты (используйте объект блокировки, который не имеет никакой другой цели).

2 голосов
/ 30 ноября 2009

Спецификация не гарантирует, что это поможет вообще:

synchronized (session) {
  Object obj = session.getAttribute(TEST_ATTR);
  if (obj==null) {
    obj = new MyObject();
    session.setAttribute(obj);
  }
}

(Может работать для определенных реализаций, но нет никаких гарантий, что он будет работать во всех контейнерах.)

Сервлет 2.5 MR6 говорит:

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

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

1 голос
/ 27 июня 2013

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

Object lock = session.getAttribute("SessionLock");
if (lock == null) {
  synchronized (MyObject.class) {
    lock = session.getAttribute("SessionLock");
    if(lock == null) {
      lock = new Object();
      session.setAttribute("SessionLock", lock);
    }
  }
}
synchronized (lock) {
  Object obj = session.getAttribute(TEST_ATTR);
  if (obj==null) {
    obj = new MyObject();
    session.setAttribute(obj);
  }
}
1 голос
/ 11 ноября 2011

Вам не нужно блокировать, поскольку session.setAttribute() является поточно-ориентированным (см. Комментарий к спецификации сервлета из @McDowell выше).

Однако, давайте использовать другой пример. Допустим, вы хотите проверить значение атрибута, а затем обновить его, если <= 100. В этом случае вам необходимо синхронизировать блок кода для <code>getAttribute(), сравнения <= 100 и <code>setAttribute().

Теперь, что вы должны использовать для блокировки? Помните, что нет синхронизации, если для блокировки используются разные объекты. Таким образом, разные блоки кода должны использовать один и тот же объект. Ваш выбор объекта session может быть просто удачным. Помните также, что различные блоки кода могут получить доступ к сеансу (как чтение / запись), даже если вы взяли блокировку, если только этот другой код также не блокируется на объекте сеанса. Подводный камень в том, что слишком много мест в вашем коде блокируют объект сеанса и, следовательно, должны ждать. Например, если ваш блок кода использует атрибут сеанса A, а другой кусок кода использует атрибут сеанса B, было бы хорошо, если бы им не нужно было ждать друг друга, взяв блокировку на объекте сеанса. Использование статических объектов с именами LockForA и LockForB может быть лучшим выбором для вашего кода, например. synchronized (LockForA) { }.

1 голос
/ 30 ноября 2009

Ваш код не будет работать по крайней мере по двум причинам.

1) Если сеанс не существует, то вы могли бы легко создать его дважды для одного и того же пользователя и иметь ужасное состояние гонки.

2) Если сеанс не является одним и тем же объектом в потоках, он все равно не будет работать. Сеанс, вероятно, будет equals() для того же сеанса в другом потоке, но это не сработает.

...