gpt4 book ai didi

java - 理解 Goetz 关于 HttpSession 的线程安全的文章

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:53:59 25 4
gpt4 key购买 nike

引用 Brian Goetz 的文章 Are all stateful Web applications broken?对于IBM developerWorks,我想引用这段代码

HttpSession session = request.getSession(true);
ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart");
if (cart == null) {
cart = new ShoppingCart(...);
session.setAttribute("shoppingCart", cart);
}
doSomethingWith(cart);

据我所知,这段代码不是线程安全的,因为它使用了check-then-act 模式。但是我有一个疑问:

第一行中 HttpSession 的创建或检索不是完全原子的吗?原子的意思是,如果两个线程调用 request.getSession(),其中一个会阻塞。尽管两者都会返回相同的 HttpSession 实例。因此,如果客户端(移动/网络浏览器)对同一个 Servlet(执行上面的代码片段)进行两次或调用,您将永远不会遇到不同线程看到 cart 的不同值的情况.

假设我确信它不是 线程安全的,如何使这个线程安全? AtomicReference 会起作用吗?例如:

HttpSession session = request.getSession(true);
AtomicReference<ShoppingCart> cartRef =
(<AtomicReference<ShoppingCart>)session.getAttribute("shoppingCart");
ShoppingCart cart = cartRef.get();
if (cart == null) {
cart = new ShoppingCart(...);
session.setAttribute("shoppingCart",
new AtomicReference<ShoppingCart>(cart));
}
doSomethingWith(cart);

谢谢!

最佳答案

你的代码仍然不是线程安全的:

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

这是因为两个 Thread 都可以获得一个 null 的 cart,创建新的购物车对象,并将它们插入到 session 中。其中一个将“获胜”,这意味着一个将设置 future 请求使用的对象,但另一个将 - 对于此请求 - 使用完全不同的 cart 对象。

要使其成为线程安全的,您需要按照您引用的文章中的惯用语做这样的事情:

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

使用上面的代码,如果两个使用相同HttpSession的Thread同时进入while循环,就不存在导致它们使用不同的数据竞争购物车 对象。

要解决 Brian Goetz 没有在文章中解决的部分问题,即首先如何将 AtomicReference 放入 session 中,有一个简单且 可能(但不保证)线程安全的方式来做到这一点。即,实现一个 session 监听器并将空的 AtomicReference 对象放入 session 的 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
}
}

只有在 session 创建时才会为每个 session 调用一次此方法,因此这是进行 session 所需的任何初始化的合适位置。不幸的是,Servlet 规范不要求在监听器中调用 sessionCreated() 和调用 service() 之间存在happens-Before 关系> 方法。所以这显然不能保证是线程安全的,并且不同 Servlet 容器之间的行为可能会有所不同。

因此,即使有一个的机会,一个给定的 session 可以同时处理多个请求,这也不够安全。最后,在这种情况下,您需要使用某种锁来初始化 session 。你可以这样做:

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);
}
}

执行完上述代码后,您的 Session 就被初始化了。 AtomicReference 保证在 session 中,并且以线程安全的方式。您可以在同一个同步块(synchronized block)中更新购物车对象(并一起免除 AtomicReference —— 只需将购物车本身放入 session 中),或者您可以更新 AtomicReference 上面显示的代码。哪一个更好取决于你需要做多少初始化,执行这个初始化需要多长时间,在同步块(synchronized block)中做一切是否会对性能造成太大的伤害(最好用a来确定探查器,而不是猜测),等等。

通常,在我自己的代码中,我只使用同步块(synchronized block)而不使用 Goetz 的 AtomicReference 技巧。如果我确定同步导致了我的应用程序中的 active 问题,那么我可能会通过使用 AtomicReference 技巧等技巧将一些更昂贵的初始化从同步块(synchronized block)中移出。

另请参阅:Is HttpSession thread safe, are set/get Attribute thread safe operations?

关于java - 理解 Goetz 关于 HttpSession 的线程安全的文章,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/750165/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com