Removing Keys in Synchronized LinkedHashMap from a Guava ListMultimap Copy

Viewed 53

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 Object1 into both Maps.
  • removes existing Object1 by insertion order from both Maps if there are too many Object1 mapped to the same Object2.
@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 forwardMap and reverseMap on 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 put needs to remove some objects)
0 Answers
Related