This is more of a question that inquires what is the approach preferred by seasoned hands in Stack OverFlow community. I want to learn about that because I don't want to write unnecessary boilerplate code. So, treat my Q as that of a newbie's.
My query is taking a cue from this question and the answers on SOF. @Nandor has clarified that AtomicReference is the way to go.
In a typical application, where a class uses a Map to store some data, updates to this Map are almost always required. I am using Vavr's HashMap implementation for the purpose. Needless to say, I am going for Immutability here.
The problem I am working on is simulated here, using a fictitious CourierSlottingMachine. It holds a map that associates an agent and the bag (perhaps a collection but we don't know because that's encapsulated) that this agent has to carry. Let's assume that folks from front-office can only add a new agent and her bag to the machine.
public class CourierSlottingMachine {
private Map<AgentID, BagOfDeliveryItems> allAgents;
// ... other members, suitable constructor and methods
public CourierDesk hereComesAnAgent(AgentID agentID,BagOfDeliveryItems bag) {
// Two front-office clerks (== two threads) add two agents
this.allAgents = this.allAgents.put(agentID,bag); // We don't mind overwriting the old bag
return (this);
}
}
What I understand is that the 'put' function works on principle of immutability.
From Vavr's Map API, the put(K,V)
:
Returns: A new Map containing these elements and that entry.
Therefore, clerk(1) adds her agent and stores the new resultant Map to this.allAgents
. So, does clerk(2). It is then possible (not always) that when the function returns with this
, only one clerk's addition is visible to the external world.
In other words, it is possible that one of front office clerks, may find later on, that the agent (and the bag) she added was missing from the slotting machine (this
).
The solution to this, again by my understanding, is that CourierSlottingMachine
should hold an AtomicRefence to the Map of allAgents
, instead of just a reference to it, like so:
public class CourierSlottingMachine {
private AtomicReference<Map<AgentID, BagOfDeliveryItems>> allAgents;
// ... other members, suitable constructor and methods
public CourierDesk hereComesAnAgent(AgentID agentID,BagOfDeliveryItems bag) {
// Two front-office clerks (== two threads) add two agents
this.allAgents = this.allAgents.updateAndGet(m -> m.put(agentID,bag));
return (this);
}
}
I have three primary questions:
Is my understanding correct?
Well the understanding is correct, the code is a little bit off. You don't need to reassign this.allAgents
(actually that's the whole point) - just the updateAndGet is enough, it operates on the same variable.
...should always use an AtomicReference instead of just the Reference to that member variable...
AtomicReference is just one of the ways, you can use locks, synchronization or other mechanisms. If performance really matters to you then you should consider other options - if you have really high contention (threads often fight for this variable) then locks will perform better than atomics. As far as patterns go, I would suggest a pattern of having your fields final - you are gonna avoid mistakes like that (overwriting a vavr list variable for example) thanks to final fields.
then do we really get any benefit of immutable collections offered by Vavr?
You do benefit, it's a completely different thing, and it's really amazing actually. I'm bad at explaining, but let's go.
Imagine you have a table (your CourierSlottingMachine
class) and there is a box (your Map
) on that table. The box is protected by a guard (AtomicReference
), so only one person can come and do something with it. The box itself is designed in a way that only one person can operate on it.
So do you really need a guard if the box is already protected? No. So in your case you basically do not benefit, could just use ConcurrentHashMap
.
But now imagine that you let people take the box with them, wherever they want (returning the Map
from your method). If one person takes your box, then he has his own copy, he can do whatever he wants with it - destroy it, cut it in half and make two boxes, whatever. Then the best part - if he wants the same box he had a couple minutes ago, he just comes in and takes it again. And other people can take it aswell and do whatever they want, and it's not gonna affect other peoples boxes. Why a guard then? Imagine you added something valuable to the box and you want your box to be on display right now - that's what the guard is for.
Now imagine a same case if the box wouldn't be a vavr box - it could but a normal java box, or even a concurrent java box. There is always only one box. People can take it, but they have to return it before anyone else is allowed to take it. No private boxes anymore, no copies, no safe point of return - someone else may even steal your items you put there before.
This analogy fell through halfway, but I hope it helps at least a little bit :)