Использование объединения строк - плохой выбор для составного ключа. Конкатенация строк - это дорогостоящая операция, которая не гарантирует уникальности, поскольку ключ становится неоднозначным, когда одна из строк содержит выбранный вами разделитель.
Конечно, вы можете запретить этот конкретный символ в именах пользователей, но это добавляет дополнительные требования, которые вы должны проверить, в то время как выделенный ключевой объект, содержащий две ссылки, проще и эффективнее. Вы даже можете использовать двухэлементный List<String>
как тип ключа add-ho c, поскольку он имеет полезные реализации hashCode
и equals
.
Но так как вы хотите выполнить поиск для обеих частей в любом случае составного ключа, вы не должны использовать составной ключ в первую очередь. Просто свяжите оба имени пользователя с одним и тем же объектом Challenge
. Это все еще не может быть сделано в одной операции atomi c, но для этого не нужно:
final ConcurrentHashMap<String, Challenge> challenges = new ConcurrentHashMap<>();
Challenge startNewChallenge(String user1, String user2) {
if(user1.equals(user2))
throw new IllegalArgumentException("same user");
Challenge c = new Challenge();
if(challenges.putIfAbsent(user1, c) != null)
throw new IllegalStateException(user1+" has an ongoing challenge");
if(challenges.putIfAbsent(user2, c) != null) {
challenges.remove(user1, c);
throw new IllegalStateException(user2+" has an ongoing challenge");
}
return c;
}
Этот код никогда не будет перезаписывать существующее значение. Если оба putIfAbsent
были успешными, то у обоих пользователей определенно не было текущих вызовов, и теперь они оба связаны с одним и тем же новым вызовом.
Когда первый putIfAbsent
успешен, но второй не пройден, мы должны удалить первый ассоциация. remove(user1, c)
удалит его только тогда, когда пользователь все еще связан с нашей новой задачей. Когда все операции на карте следуют принципу никогда не перезаписывать существующую запись (если не выполнены все предварительные условия), в этом нет необходимости, просто remove(user1)
также подойдет. Но использование безопасного варианта здесь не помешает.
Единственная проблема с неатомичностью состоит в том, что две попытки перекрытия с участием одного и того же пользователя могут потерпеть неудачу из-за временно добавленного первого пользователя, когда на самом деле один из них может добиться успеха. Я не считаю это серьезной проблемой; пользователь просто не должен пытаться присоединиться к двум вызовам одновременно.