Понимание статьи Goetz о Потокобезопасности HttpSession

Если вы не хотите, чтобы падение внутри пролета, вы можете предотвратить падение с этого узла. <span class="drop" ondrop="event.stopPropagation();" Это гарантирует, что ваш обработчик на содержащем div не будет вызван.

function allowDrop(ev) {
  ev.preventDefault();
}

function drag(ev) {
  ev.dataTransfer.setData("text", ev.target.id);
}

function drop(ev) {
  ev.preventDefault();
  var data = ev.dataTransfer.getData("text");
  ev.target.appendChild(document.getElementById(data));
}
#div1,
#div2 {
  float: left;
  width: 100%;
  height: 100%;
  margin: 10px;
  padding: 10px;
  display: inline-block;
}
.drop {
width:100px;
height:50px;
display:block;
border:1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div style="background:#fafafa;width:100%;height:440px;padding-top:10px;">
  <div style="text-align:center;width:49%;height: 100%;display:inline-block;border-right:1px solid #e0e0e0;">
    <div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)">
      <span class="drop" draggable="true" ondragstart="drag(event)" id="drag1" data-orderID="1">SPAN 1</span>
    </div>
  </div>
  <div style="text-align:center;width:49%;height: 100%;display:inline-block;">
    <div id="div2" ondrop="drop(event)" ondragover="allowDrop(event)">
      <span class="drop" ondrop="event.stopPropagation();" draggable="true" ondragstart="drag(event)" id="drag2"  data-orderID="2">SPAN 2</span>
    </div>
  </div>
</div>

10
задан reevesy 22 April 2017 в 23:12
поделиться

4 ответа

Ваш код по-прежнему не является потокобезопасным:

ShoppingCart cart = cartRef.get();
if (cart == null) {
    cart = new ShoppingCart(...);
    session.setAttribute("shoppingCart",
         new AtomicReference<ShoppingCart>(cart));
}

Это потому, что два потока могут оба получить cart of null, создайте новые объекты корзины покупок и вставьте их в сеанс. Один из них «победит», то есть один установит объект, используемый будущими запросами, а другой - для этого запроса - использует совершенно другой объект cart .

Чтобы создать этот поток -safe, вам нужно будет сделать что-то вроде этого, следуя идиоме из статьи, на которую вы ссылались:

while (true) {
    ShoppingCart cart = cartRef.get();
    if (cart != null) {
        break;
    }
    cart = new ShoppingCart(...);
    if (cartRef.compareAndSet(null, cart))
        break;
} 

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

Для решения той части проблемы, которую не делает Брайан Гетц В этой статье, а именно, как вы в первую очередь получаете AtomicReference в сеанс, есть простой и , вероятно, (но не гарантированный) потокобезопасный способ сделать это , А именно, реализуйте слушатель сеанса и поместите пустые объекты AtomicReference в сеанс в его методе sessionCreated :

public class SessionInitializer implements HttpSessionListener {
  public void sessionCreated(HttpSessionEvent event){
    HttpSession session = event.getSession();
    session.setAttribute("shoppingCart", new AtomicReference<ShoppingCart>());
  }
  public void sessionDestroyed(HttpSessionEvent event){
    // No special action needed
  }
}

Этот метод будет вызываться один раз для каждого сеанса, только при его создании так что это подходящее место для инициализации, необходимой для сеанса. К сожалению, спецификация сервлета не требует наличия отношения случай-до между вызовом sessionCreated () в вашем слушателе и вызовом вашего метода service () . Так что это, очевидно, не гарантированно является потокобезопасным, и может потенциально различаться по поведению между различными контейнерами сервлетов.

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

HttpSession session = request.getSession(true);
AtomicReference<ShoppingCart> cartRef;
// Ensure that the session is initialized
synchronized (lock) {
    cartRef = (<AtomicReference<ShoppingCart>)session.getAttribute("shoppingCart");
    if (cartRef == null) {
        cartRef = new AtomicReference<ShoppingCart>();
        session.setAttribute("shoppingCart", cartRef);
    }
}

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

Обычно в своем собственном коде я просто использую синхронизированный блок и не использую трюк Гетца AtomicReference . Если бы я когда-либо определил, что синхронизация вызывает проблемы с живучестью в моих приложениях, то я потенциально мог бы переместить некоторые более дорогие инициализации из синхронизированных блоков, используя приемы, такие как трюк AtomicReference .

См. Также: Безопасен ли поток HttpSession, Атрибуты set / get Потокобезопасные операции?

8
ответ дан 3 December 2019 в 23:15
поделиться

Разве это не создание или поиск HttpSession в первой строке полностью атомное? Под атомным я подразумеваю, что если два Потоки вызывают request.getSession (), один будет блокироваться.

Даже если getSession блокирует, как только один поток возвращается с сеансом, блокировка снимается. Пока он создает новую корзину, другие потоки могут получить блокировку, получить сеанс и обнаружить, что в сеансе пока нет корзины.

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

К сожалению, ваше предлагаемое решение выполняет одно и то же: проверка объекта в сеансе и публикация один при необходимости, но без какой-либо блокировки. Тот факт, что атрибут сеанса является AtomicReference , не имеет значения.

Чтобы сделать это безопасно, вы можете использовать что-то вроде Goetz '"Listing 5" , где чтение и запись в атрибут сеанса выполняются во время синхронизации с общей блокировкой.

HttpSession session = request.getSession();
ShoppingCart cart;
synchronized (lock) {
  cart = (ShoppingCart) session.getAttribute(ATTR_CART);
  if (cart == null) {
    cart = new ShoppingCart();
    session.setAttribute(ATTR_CART, cart);
  }
}

Обратите внимание, что в этом примере предполагается, что ShoppingCart является изменяемой и поточно-ориентированной.

3
ответ дан 3 December 2019 в 23:15
поделиться

Прошло несколько лет с тех пор, как я что-то здесь делал с сервлетами Java, поэтому ухожу из памяти.

Я ожидаю, что проблема безопасности потока здесь в проверке для cart == null. При рассмотрении проблем с потоками нужно понимать, что поток может быть прерван между ЛЮБЫМИ двумя машинными инструкциями (а не просто какой-либо строкой кода). То есть даже

i += 1;

не является поточно-ориентированным (если я в любом случае совместно используется), так как i + = 1 - это (как минимум) две инструкции: добавление и хранилище. Поток может быть прерван между надстройкой и хранилищем, и выживет только одна из надстроек.

То же самое происходит в этом примере. Предположим на мгновение, два потока делают запрос в одном и том же сеансе (например, как Гетц предлагает из кадров или запросов ajax). Один входит в этот раздел кода, успешно получает HttpSession, затем пытается получить атрибут shoppingCart. Однако, поскольку он еще не существует, возвращается ноль. Затем поток прерывается другим запросом, который делает то же самое. Это также становится нулевым. Затем эти два запроса выполняются в любой последовательности, поскольку оба получили нулевую ссылку для атрибута shoppingCart, поскольку корзина не была сохранена в это время, оба потока создадут новый объект Cart и оба попытаются его сохранить. Один потеряет, и эти изменения в Корзине будут потеряны. Таким образом, этот код не является потокобезопасным.

Что касается второй половины вашего вопроса, я не знаком с объектом AtomicReference. Я быстро просмотрел API Java для AtomicReference, и он мог бы работать, но я не уверен. В любом слючае. Самое очевидное решение, которое я могу придумать, это использовать монитор. По сути, вы хотите сделать взаимное исключение в части кода get-test-set вашего кода.

Теперь, при условии, что ваш объект корзины является атомарным (т. Е. Нам нужно только защитить его получение и настройку, Я думаю, что-то вроде этого может сработать:

public syncronized ShoppingCart atomicGetCart(HttpSession session){    
    ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
    if (cart == null) {
        cart = new ShoppingCart(...);
        session.setAttribute("shoppingCart", cart);
    }

    return cart;
}

HttpSession session = request.getSession(true);
ShoppingCart cart = atomicGetCart
doSomethingWith(cart);

Так вот, я не очень разбираюсь в производительности мониторов Java, поэтому я не уверен, какие издержки это может вызвать. Кроме того, это должно быть единственное место, где В основном, ключевое слово syncronized означает, что только один поток может войти в метод atomicGetCart за раз. Блокировка используется для обеспечения этого (блокировка - это просто объект, которым может одновременно владеть только один поток). Таким образом, у вас больше не будет состояния гонки, которое было в другом коде.

Надеюсь, это поможет, Я думаю, что-то вроде этого может сработать:

public syncronized ShoppingCart atomicGetCart(HttpSession session){    
    ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
    if (cart == null) {
        cart = new ShoppingCart(...);
        session.setAttribute("shoppingCart", cart);
    }

    return cart;
}

HttpSession session = request.getSession(true);
ShoppingCart cart = atomicGetCart
doSomethingWith(cart);

Теперь, я не знаю много о производительности мониторов Java, поэтому я не уверен, какие издержки это может понести. Кроме того, это должно быть единственное место, где можно получить корзину. По сути, ключевое слово syncronized означает, что только один поток может одновременно вводить метод atomicGetCart. Для обеспечения этого используется блокировка (блокировка - это просто объект, которым может одновременно владеть только один поток). Таким образом, у вас больше не будет состояния гонки, которое было в другом коде.

Надеюсь, это поможет, Я думаю, что-то вроде этого может сработать:

public syncronized ShoppingCart atomicGetCart(HttpSession session){    
    ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
    if (cart == null) {
        cart = new ShoppingCart(...);
        session.setAttribute("shoppingCart", cart);
    }

    return cart;
}

HttpSession session = request.getSession(true);
ShoppingCart cart = atomicGetCart
doSomethingWith(cart);

Теперь, я не знаю много о производительности мониторов Java, поэтому я не уверен, какие издержки это может понести. Кроме того, это должно быть единственное место, где можно получить корзину. По сути, ключевое слово syncronized означает, что только один поток может одновременно вводить метод atomicGetCart. Для обеспечения этого используется блокировка (блокировка - это просто объект, которым может одновременно владеть только один поток). Таким образом, у вас больше не будет состояния гонки, которое было в другом коде.

Надеюсь, это поможет, Ключевое слово syncronized означает, что только один поток может одновременно вводить метод atomicGetCart. Для обеспечения этого используется блокировка (блокировка - это просто объект, которым может одновременно владеть только один поток). Таким образом, у вас больше не будет состояния гонки, которое было в другом коде.

Надеюсь, это поможет, Ключевое слово syncronized означает, что только один поток может одновременно вводить метод atomicGetCart. Для обеспечения этого используется блокировка (блокировка - это просто объект, которым может одновременно владеть только один поток). Таким образом, у вас больше не будет состояния гонки, которое было в другом коде.

Надеюсь, это поможет, -Daniel

2
ответ дан 3 December 2019 в 23:15
поделиться

Не хочу пересекаться, но я написал комментарий к этой статье и не получил ответа от автора. Глядя на другие статьи Брайана Гетца на сайте IBM, кажется, что он не хочет ни на что отвечать.

Я думаю, что код, который он предложил в листинге 5 своей статьи, не работает.

Предположим, что текущий наивысший балл составляет 1000 и 2 одновременных запроса с баллом 1100 и 1200 находятся в процессе. Оба запроса получают наивысшую оценку за одно и то же время:

PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");

что заставляет оба потока рассматривать hs как 1000. После этого один из потоков входит в синхронизированную секцию, если условие выполнено, новое значение (скажем, 1200) устанавливается для атрибута servletcontext и секция синхронизации заканчивается. Затем второй поток входит в синхронизированный раздел и все еще видит предыдущее значение hs - hs все еще равно 1000.Если условие выполнено (конечно, так как 1100> 1000), новое значение (1100) устанавливается в servletcontext. Разве

PlayerScore hs = (PlayerScore) ctx.getAttribute("highScore");

не должен принадлежать синхронизированному разделу?

1
ответ дан 3 December 2019 в 23:15
поделиться