Ваш код по-прежнему не поддерживает поток:
ShoppingCart cart = cartRef.get();
if (cart == null) {
cart = new ShoppingCart(...);
session.setAttribute("shoppingCart",
new AtomicReference<ShoppingCart>(cart));
}
Это связано с тем, что два потока могут одновременно получить значение cart
, равное нулю, создать новые объекты корзины покупок и вставить их в сеанс. Один из них «победит», то есть один будет устанавливать объект, используемый будущими запросами, а другой будет - для этого запроса - использовать совершенно другой объект cart
.
Чтобы сделать этот потокобезопасным, вам нужно сделать что-то вроде этого, следуя идиоме из статьи, на которую вы ссылались:
while (true) {
ShoppingCart cart = cartRef.get();
if (cart != null) {
break;
}
cart = new ShoppingCart(...);
if (cartRef.compareAndSet(null, cart))
break;
}
При использовании приведенного выше кода, если два потока, использующие один и тот же HttpSession
, одновременно входят в цикл while
, не существует гонки данных, которая могла бы заставить их использовать разные объекты 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, безопасны ли операции установки / получения атрибута потока?