Вопрос проверяет понимание атомарных операций и скрытых гонок при инициализации значений.
computeIfAbsent() атомарно проверяет наличие ключа и создаёт значение.putIfAbsent() требует предварительного создания объекта.
Это может привести к лишним вычислениям.computeIfAbsent() предотвращает гонки и дублирование логики.
Он особенно полезен при дорогой инициализации.
Разница между этими методами часто недооценивается.
putIfAbsent()Object lock = new Object();
locks.putIfAbsent(id, lock);
Недостатки:
объект создаётся всегда
при гонке несколько потоков создадут лишние объекты
логика инициализации не атомарна
computeIfAbsent()Object lock = locks.computeIfAbsent(id, k -> new Object());
Преимущества:
инициализация происходит только при отсутствии ключа
операция атомарна
код короче и безопаснее
дорогая инициализация (IO, конфигурация)
хранение локов
кеширование вычисляемых значений
функция инициализации не должна иметь побочных эффектов
не гарантируется единичный вызов при сложных сценариях (важно читать контракт)
computeIfAbsent() предпочтительнее, когда важно атомарно и лениво создавать значения.