Reputation: 7878
I'm wondering what is the best practice for making a session bean thread safe.
Let's assume I have this session bean and its service:
@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);
}
}
The code above is not thread safe and will cause issues when multiple threads (of the same session, eg. by asynchronous Ajax-requests) perform a CartService.addItem(Item)
.
I think I'm not the first with this problem, but my researches didn't bring me to a best practice.
The worst thing I could do would synchronize addItem() as CartService is shared by multiple sessions. Synchronizing on cart in CartService.addItem()
seems to me similarly bad, as Cart is a proxied bean. Which I understand as all sessions would still synchronize on the same object.
One acceptable solution seems to be a synchronized block on Cart.getItems()
in CartService.addItem()
:
@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);
}
}
}
Is there any best practice? Maybe spring has something to offer for this problem?
Upvotes: 6
Views: 3903
Reputation: 7878
After digging a bit through the Spring API I found RequestMappingHandlerAdapter.setSynchronizeOnSession(boolean)
which seems to synchronize every controller on a session mutex. This might be overkill. But it makes at least controllers thread safe on the session without blocking other users, plus I don't have to worry about synchronization in my controller. But this is still not acceptable for a highly responsive Ajax-GUI.
I get the feeling that there is no general answer to this question and it totally depends on the GUI. If I have plain simple HTML pages where sequential requests are expected RequestMappingHandlerAdapter.setSynchronizeOnSession(true)
seems to be perfect, as I don't have to think about synchronization in my Controllers.
If the GUI becomes more fancy with loads of parallel AJAX-requests I have to take care about synchronization by choosing an eligible mutex.
Upvotes: 2
Reputation: 5066
Firstly I'm assuming that you meant to say that the Cart
bean is Session scoped (your example says that it is request scoped, but that doesn't seem to make sense) and that CartService
is a singleton.
If we think about how your user interacts with the application, do we actually have a need to perform any synchronization on a Cart
instance?
We definitely do not need to make CartService.addItem()
synchronized as we're relying on Spring's proxy injection to ensure that the Cart
instance from the current Session is injected during the Thread of execution, so no worries there.
So our question of synchronization is about whether there is a scenario during a single Session that requires us to synchronize adding of items to a Cart
?
I assume that your Session is being driven by the user's browser. There is no way legitimate way to share a single Session between different browsers. So apart from having multiple tabs open, it is highly likely that your user will be adding items to their Cart
in a sequential manner. Even if they did open multiple tabs and enter items is there a problem? Once the user initiates your "checkout" process, you can block adding more items to the Cart
and then ask the user to confirm that the Cart
they are about to checkout is correct, giving them the opportunity to remove any dubious items.
If you still decide that you really need synchronization on the Cart
then I would do the following:
public class Cart
{
private HashSet<Item> items = new HashSet<Item>();
public synchronized void addItem(Item item)
{
items.add(item);
}
}
That way you easily synchronize access to the Set
within the Cart
class.
Upvotes: -1