Как атомарные (Atomic) переменные решают проблему состояния гонки (Race Condition) в Java?

«Как атомарные (Atomic) переменные решают проблему состояния гонки (Race Condition) в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Классы из пакета java.util.concurrent.atomic (например, AtomicInteger, AtomicReference) решают проблему состояния гонки, предоставляя атомарные операции над отдельными переменными.

Ключевой механизм — Compare-And-Swap (CAS): Операции выполняются по принципу "прочитал значение, вычислил новое, обновил, если исходное не изменилось". Это реализовано на уровне процессорных инструкций, что делает операции неразрывными (атомарными) без использования тяжеловесных блокировок (synchronized).

Пример решения проблемы гонки:

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    // Потокобезопасный инкремент без synchronized
    public void increment() {
        count.incrementAndGet(); // Атомарная операция
    }

    public int getCount() {
        return count.get();
    }
}

Преимущества и особенности:

  • Атомарность: Операции вроде getAndIncrement(), compareAndSet() выполняются как единое целое.
  • Видимость: Как и volatile, обеспечивают happens-before гарантии — изменения, сделанные одним потоком, сразу видны другим.
  • Производительность: CAS часто эффективнее блокировок при низкой или средней конкуренции потоков.
  • Ограничение: Подходят для атомарных операций над одной переменной. Для сложных составных действий (например, проверка-действие над несколькими полями) все еще необходимы блокировки или более высокоуровневые примитивы (ReentrantLock).