java – 了解Goetz关于HttpSession线程安全性的文章

前端之家收集整理的这篇文章主要介绍了java – 了解Goetz关于HttpSession线程安全性的文章前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
关于IBM developerWorks的Brian Goetz的文章 Are all stateful Web applications broken?,我想参考这段代码
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在第一行的创建或检索是不是完全是原子的?按原子,我的意思是如果两个Threads调用request.getSession(),一个将被阻塞.虽然都会返回相同的HttpSession实例.因此,如果一个客户端(移动/网络浏览器)进行两个或者调用同一个Servlet(执行上面的代码片段),那么不会有不同的线程看到不同的购物车的值.

假设我确信这不是线程安全的,那么这个线程如何安全呢? 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);

留言Merci!

解决方法

你的代码仍然不是线程安全的:
ShoppingCart cart = cartRef.get();
if (cart == null) {
    cart = new ShoppingCart(...);
    session.setAttribute("shoppingCart",new AtomicReference<ShoppingCart>(cart));
}

这是因为两个线程都可以获得一个零车,创建新的购物车对象,并将它们插入到会话中.其中一个将“赢”,意味着将设置未来请求使用的对象,但另一个将 – 为此请求 – 使用完全不同的购物车对象.

要使这个线程安全,您需要执行类似的操作,遵循引用的文章中的成语:

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

使用上述代码,如果使用相同HttpSession的两个线程同时进入while循环,则不会导致它们使用不同的cart对象的数据竞争.

为了解决Brian Goetz在文章中没有解决的问题的一部分,即如何将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
  }
}

对于每个会话,这个方法将被调用一次,只有当它被创建时,所以这是执行会话所需的任何初始化的适当的地方.不幸的是,Servlet规范并不要求在侦听器中调用sessionCreated()并调用service()方法之间发生关系.所以这显然不能保证线程安全,并且可能会在不同Servlet容器之间的行为有所不同.

因此,如果给定的会话一次只能有少于几次飞行中的请求,这是不够安全的.最终,在这种情况下,您需要使用某种类型的锁来初始化会话.你可以这样做:

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.哪个更好取决于需要做多少初始化,执行此初始化需要多长时间,是否在同步块中执行所有操作会对性能造成太大的损害(最好用分析器确定,而不是猜测),等等.

通常,在我自己的代码中,我只使用一个同步块,不要使用Goetz的AtomicReference技巧.如果我曾经确定同步在我的应用程序中引起了一个活跃的问题,那么我可能通过使用像AtomicReference技巧这样的技巧,从同步块中移除一些更昂贵的初始化.

参见:Is HttpSession thread safe,are set/get Attribute thread safe operations?

原文链接:https://www.f2er.com/java/123282.html

猜你在找的Java相关文章