I have a forwardMap that defines a one-to-one relationship and uses ListMultimap to store the inverted forwardMap that has a one-to-many relationship:
class Object1 { // with custom equals and hashCode }
class Object2 { // with custom equals and hashCode }
class ObjectManager {
Map<Object1, Object2> forwardMap =
Collections.synchronizedMap(new LinkedHashMap<>());
ListMultimap<Object2, Object1> reverseMap =
Multimaps.invertFrom(Multimaps.forMap(forwardMap), LinkedListMultimap.create());
}
I'd like to implement put method in ObjectManager that
- puts an
Object1into both Maps. - removes existing
Object1by insertion order from both Maps if there are too manyObject1mapped to the sameObject2.
@Synchronized
public void put(Object1 newForwardKey) {
Optional<Object1> _closestForwardKey =
forwardMap.keySet().stream()
.min(Comparator.comparingDouble(o -> calculateDistance(o, newForwardKey)));
// assign matchedForwardValue to a new Object2 by default
Object2 matchedForwardValue = new Object2();
if (_closestForwardKey.isPresent()) {
Object1 closestForwardKey = _closestForwardKey.get();
// reset forwardValue only if closestForwardKey passes some other matching
if (someOtherMatching(closestForwardKey, newForwardKey))
matchedForwardValue = forwardMap.get(closestForwardKey);
}
// create appropriate entries for each map
forwardMap.put(newForwardKey, matchedForwardValue);
reverseMap.put(matchedForwardValue, newForwardKey);
// remove excessive Object1
// modification to the forwardKeysByValue List view is reflected in reverseMap
List<Object1> forwardKeysByValue = reverseMap.get(forwardValue);
while (forwardKeysByValue.size() > MAX_REVERSE_VALUE_COUNT)
forwardMap.remove(forwardKeysByValue.remove(0));
}
I did not test it in a multithreaded environment. Running the method in a single thread seems to have the correct behavior.
Test code:
Object2 o21 = new Object2(1);
Object2 o22 = new Object2(2);
for (int i = 0; i < 10; i++) {
Object1 o11 = new Object1(i);
Object1 o12 = new Object1(i+10);
objectManagerInstance.put(o11); // assume always match o21
objectManagerInstance.put(o12); // assume always match o22
}
Contents of both Maps:
// forwardMap
Object1(4) -> Object2(1)
...
Object1(9) -> Object2(1)
Object1(14) -> Object2(2)
...
Object1(19) -> Object2(2)
// reverseMap
Object2(1) -> [Object1(4) ... Object1(9)]
Object2(2) -> [Object1(14) ... Object2(19)]
I would appreciate your help explaining:
- Is there a thread-safe implementation that doesn't synchronize both
forwardMapandreverseMapon object level? Or this kind of action needs to "stop the world"?
Edit 2
- Refactored code.
- Self-answered how to make original code more memory efficient (original code generates a reverseMap each time
putneeds to remove some objects)