微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐