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!
您的代码仍然不是线程安全的:
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线程是否安全,是否设置/获取属性线程安全操作?
| 归档时间: |
|
| 查看次数: |
5885 次 |
| 最近记录: |