Зачем использовать атомарные переменные, если каждый HTTP-запрос обрабатывается в одном потоке?

«Зачем использовать атомарные переменные, если каждый HTTP-запрос обрабатывается в одном потоке?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Атомарные переменные (AtomicInteger, AtomicLong и др.) решают две ключевые задачи, актуальные даже при обработке одного запроса за раз:

  1. Гарантия видимости изменений (happens-before): Изменение атомарной переменной в одном потоке гарантированно становится видимым для всех последующих потоков. Хотя запрос R1 выполняется в потоке Thread-A, запрос R2 может быть обработан в Thread-B. Без атомарности/volatile обновление, сделанное в R1, может не отобразиться в R2 из-за кэширования в памяти потока.
  2. Атомарность составных операций: Операции вроде incrementAndGet() выполняются как единое неделимое действие (чтение-модификация-запись). Это критично для корректного подсчета, даже если параллелизма нет.

Пример счетчика запросов:

private final AtomicInteger requestCounter = new AtomicInteger(0);

@GetMapping("/api/visit")
public int visit() {
    // Гарантированно вернет уникальное и корректное значение для каждого запроса
    return requestCounter.incrementAndGet();
}

Использование обычного int в этом сценарии может привести к тому, что некоторые инкременты "потеряются" для других потоков.