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

Jac*_*ine 10 java concurrency multithreading servlets

参考Brian Goetz的文章是否所有状态Web应用程序都被破坏了?对于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);
Run Code Online (Sandbox Code Playgroud)

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

是不是HttpSession在第一行完全原子的创建或检索?原子,我的意思是如果两个线程调用request.getSession(),一个将阻止.虽然两者都会返回相同的HttpSession实例.因此,如果客户端(移动/ Web浏览器)制作两个或调用相同的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);
Run Code Online (Sandbox Code Playgroud)

留言Merci!

Edd*_*die 8

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

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

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

为了使这个线程安全,你需要做这样的事情,遵循你引用的文章中的习语:

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

使用上面的代码,如果两个使用相同的线程同时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
  }
}
Run Code Online (Sandbox Code Playgroud)

只有在创建时,才会为每个会话调用此方法一次,因此这是进行会话所需的任何初始化的适当位置.不幸的是,Servlet规范并不要求在监听器中调用和调用方法之间存在一个before-Before关系.所以这显然不能保证是线程安全的,并且可能在不同的Servlet容器之间的行为上有所不同.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

执行上述代码后,会话初始化.该AtomicReference保证是在会话,并在一个线程安全的方式.您可以在同一个同步块中更新购物车对象(并将AtomicReference所有内容放在一起 - 只需将购物车本身放入会话中),或者您可以更新AtomicReference上面显示的代码.哪个更好取决于你需要做多少初始化,执行这个初始化需要多长时间,是否在同步块中做所有事情都会对性能造成太大影响(最好通过分析器确定,而不是猜测) , 等等.

通常,在我自己的代码中,我只使用同步块而不使用Goetz的AtomicReference技巧.如果我确定同步导致我的应用程序中存在活动问题,那么我可能会使用类似AtomicReference技巧的技巧从同步块中移除一些更昂贵的初始化.

另请参阅:HttpSession线程是否安全,是否设置/获取属性线程安全操作?