Я хотел обсудить конкретное использование параллельной карты, чтобы проверить мою логику...
Если бы я использовал ConcurrentHashMap
, я мог бы сделать фамильяр
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();
public V getExampleOne(K key) {
map.putIfAbsent(key, new Object());
return map.get(key);
}
но я понимаю, что существует условие гонки, при котором, если я удалю элемент с карты между putIfAbsent
и get
, описанный выше метод вернет что-то, чего больше нет в коллекции. Это может быть хорошо, а может и нет, но давайте предположим, что для моего варианта использования это не нормально.
Чего бы я действительно хотел, так это чтобы все это было атомарным. Так,
public V getExampleTwo(K key) {
return map.putIfAbsent(key, new Object());
}
но поскольку это расширяется до
if (!map.containsKey(key))
return map.put(key, value); [1]
return map.get(key);
который для строки [1] вернет null
при первом использовании (т. е. map.put
вернет предыдущее значение, которое при первом использовании равно null
).
В этом случае я не могу вернуть null
Что оставляет меня с чем-то вроде;
public V getExampleThree(K key) {
Object object = new Object();
V value = locks.putIfAbsent(key, object);
if (value == null)
return object;
return value;
}
Итак, наконец, мой вопрос; чем примеры выше отличаются по семантике?. Обеспечивает ли getExampleThree
атомарность, аналогичную getExampleTwo
, но правильно избегает возврата null? Есть ли другие проблемы с getExampleThree
?
Я надеялся на небольшое обсуждение выбора. Я понимаю, что мог бы использовать не ConcurrentHashMap
и синхронизироваться вокруг клиентов, вызывающих мой метод get
и метод для удаления с карты, но это, похоже, противоречит цели (неблокирующий характер) ConcurrentHashMap. Это единственный способ обеспечить точность данных?
Я предполагаю, что это часть того, почему вы выбрали ConcurrentHashMap; что он виден/актуален/точен в тот момент, когда вы взаимодействуете с ним, но может быть влияние в дальнейшем, если старые данные станут проблемой...