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

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

Ответ

Нет, volatile не решает проблему состояния гонки (Race Condition). Оно решает другую проблему — видимость изменений между потоками.

Что гарантирует volatile:

  • Видимость: Запись в volatile-переменную одним потоком становится немедленно видна всем другим потокам.
  • Запрет переупорядочивания: Операции чтения/записи volatile-переменной не могут быть переупорядочены компилятором или процессором относительно других операций памяти.

Почему volatile недостаточно для Race Condition: Race Condition возникает при неатомарных составных операциях (read-modify-write). Ключевое слово volatile не делает саму операцию атомарной.

Пример проблемы (инкремент — неатомарная операция):

public class Counter {
    private volatile int count = 0; // volatile НЕ помогает!

    public void increment() {
        count++; // Операция состоит из: 1) read count, 2) increment, 3) write count
    }
}

Два потока могут одновременно прочитать одно и то же значение count, увеличить его и записать обратно, что приведет к потере одного из обновлений.

Правильные решения для устранения Race Condition:

  1. Синхронизация (synchronized):
    public synchronized void increment() {
        count++;
    }
  2. Атомарные классы (java.util.concurrent.atomic):

    private final AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet(); // Атомарная операция
    }
  3. Примитивы синхронизации из java.util.concurrent (например, ReentrantLock).

Вывод: Используйте volatile для простых флагов или переменных состояния, где важна видимость, но для составных операций требуются механизмы, обеспечивающие атомарность.