Суть
В Effective Java (третье издание) автор предоставил совет по повышению производительности в отношении ConcurrentHashMap
--- т. е. при использовании existingValue = map.putIfAbsent(key, value)
следует сначала вызвать existingValue = map.get(key)
и пропустить putIfAbsent()
, если ключ уже существует.
Вопрос
Задокументированы ли где-нибудь соображения производительности, подобные упомянутым автором?
Я считаю, что это достаточно важное/фундаментальное соображение производительности, которое должно быть задокументировано где-то официально, тем более что putIfAbsent()
уже возвращает значение, если ключ уже существует, что делает дополнительное get()
избыточным, и вполне разумно, что кто-то, не знающий о соображении производительности, может отрефакторить проверку get()
.
Отредактировано, чтобы прояснить вопрос: я не спрашиваю, почему и действительно ли putIfAbsent()
всегда более эффективен, но спрашиваю, задокументированы ли где-нибудь такие соображения производительности, учитывая, что API был разработан таким образом, что на простой интерпретации putIfAbsent()
и get()
используется вместе, как предложено автором без учета соображений производительности, get()
кажется излишним.
Подробности
Конкретный пример, приведенный в книге, выглядит следующим образом:
- предполагая, что мы заинтересованы в реализации метода, похожего на
String.intern()
, который извлекает значение определенного ключа на карте и, возможно, вставляет значение ключа в карту, если оно еще не существует, - тогда более эффективный подход состоит в том, чтобы не использовать предоставленный
previousValue = map.putIfAbsent(key, value)
напрямую, а предварительно выполнить дополнительную проверкуpreviousValue = map.get(key)
.
Например, приведенный ниже код менее эффективен:
// Concurrent canonicalizing map atop ConcurrentMap - not optimal
private static final ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
public static String intern(String s) {
String previousValue = map.putIfAbsent(s, s);
return previousValue == null ? s : previousValue;
}
Принимая во внимание, что этот фрагмент кода ниже более эффективен:
// Concurrent canonicalizing map atop ConcurrentMap - faster!
public static String intern(String s) {
String result = map.get(s);
if (result == null) {
result = map.putIfAbsent(s, s);
if (result == null) result = s;
}
return result;
}
Причина, указанная автором, заключается в том, что метод get()
более оптимизирован, чем putIfAbsent()
, что я интерпретирую как означающее, что обычно стоит добавлять дополнительные проверки get()
, чтобы время от времени избегать вызова putIfAbsent()
.
Я бы также предположил, что фактическое влияние на производительность зависит от относительной частоты вставки новых ключей.