Spring中线程安全的会话Bean最佳实践是什么?

6

我想知道制作会话Bean的最佳实践方式如何保证线程安全。

假设我有这个会话Bean及其服务:

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
class Cart {

    private HashSet<Item> items = new HashSet<Item>();

    private int counter = 0;

    public HashSet<Item> getItems() {
        return items;
    }

    public int getCounter() {
        return counter;
    }

}

@Service
class CartService {

    @Autowired
    private Cart cart;

    public void addItem(Item item) throws FullException {
        if (cart.getCounter() > 1234) {
            throw new FullException();

        }
        cart.getItems().add(item);
    }

}

上述代码不支持线程安全,在多个线程(同一个会话,例如异步Ajax请求)执行CartService.addItem(Item)时会出现问题。
我认为我不是第一个遇到这个问题的人,但我的研究没有带给我最佳实践。
最糟糕的是,我可以在CartService中使addItem()同步,因为CartService被多个会话共享。在CartService.addItem()中对cart进行同步看起来对我来说同样糟糕,因为Cart是一个代理bean。这意味着我理解所有会话仍将在同一个对象上进行同步。
一种可接受的解决方案似乎是在CartService.addItem()中对Cart.getItems()进行同步块:
@Service
class CartService {

    @Autowired
    private Cart cart;

    public void addItem(Item item) {
        synchronized(cart.getItems()) {
            if (cart.getCounter() > 1234) {
                throw new FullException();

            }
            cart.getItems().add(item);
        }
    }

}

有最佳实践吗?也许 Spring 在这个问题上有所建树?

2个回答

2

在查阅Spring API后,我发现RequestMappingHandlerAdapter.setSynchronizeOnSession(boolean)可以将每个控制器与会话互斥体同步。这可能有些过度了。但至少可以使控制器在线程上对会话进行安全操作而不会阻止其他用户,同时我也不必担心控制器中的同步问题。但是,对于高响应的Ajax-GUI来说,这仍然是不可接受的。

我觉得这个问题没有通用答案,完全取决于GUI。如果我有简单的HTML页面,期望顺序请求RequestMappingHandlerAdapter.setSynchronizeOnSession(true)似乎是完美的选择,因为我不必考虑控制器中的同步。

如果GUI变得更加花哨,有大量并行的AJAX请求,我必须通过选择合适的互斥体来处理同步问题。


-1

首先,我假设您的意思是 Cart bean 是 Session scoped(您的示例说它是 request scoped,但这似乎没有意义),而 CartService 是单例。

如果我们考虑用户如何与应用程序交互,我们是否真的需要对 Cart 实例执行任何同步操作呢?

我们绝对不需要使 CartService.addItem() 同步,因为我们依赖于 Spring 的代理注入来确保在执行线程期间注入当前 Session 中的 Cart 实例,所以不必担心。

因此,我们关于同步的问题是,在单个 Session 中是否存在需要同步添加项目到 Cart 的情况?

我假设您的Session是由用户的浏览器驱动的。没有合法的方法可以在不同的浏览器之间共享单个Session。因此,除了打开多个选项卡外,您的用户很可能会按顺序向其购物车中添加项目。即使他们打开了多个选项卡并输入了项目,也有问题吗?一旦用户启动您的“结账”过程,您可以阻止添加更多项目到购物车,然后要求用户确认他们即将结账的购物车是否正确,并给他们机会删除任何可疑的项目。

如果您仍然决定确实需要在购物车上进行同步,则应执行以下操作:

public class Cart
{
     private HashSet<Item> items = new HashSet<Item>();

     public synchronized void addItem(Item item)
     {
           items.add(item);
     }
}

这样你就可以轻松地在Cart类中同步访问Set


你的假设是正确的。我在我的问题中编辑了范围。我只想快速地说,有很多情况下,多个线程访问一个会话作用域的bean。将同步放入模型bean中并不是真正可接受的,因为服务中更容易出现并发问题。 - Markus Malkusch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接