Как ключевое слово volatile в Java решает проблемы видимости и упорядочивания в многопоточности?

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

Ответ

Ключевое слово volatile гарантирует две важные вещи в многопоточном программировании Java:

  1. Видимость изменений (Memory Visibility): Запись в volatile-переменную одним потоком становится немедленно видна всем другим потокам. Без volatile изменения могут остаться в локальном кэше процессора потока.
  2. Запрет переупорядочивания (Prevents Reordering): Компилятору и процессору запрещено переставлять операции чтения/записи volatile-переменной относительно других операций с памятью, что обеспечивает соблюдение happens-before.

Типичный use-case — флаг остановки потока:

public class WorkerThread implements Runnable {
    private volatile boolean running = true;

    public void run() {
        while (running) { // Чтение всегда увидит последнее значение
            // выполнение работы
        }
    }

    public void stop() {
        running = false; // Запись видна потоку run() немедленно
    }
}

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

  • Атомарность составных операций: Операции вроде count++ (чтение-изменение-запись) не атомарны, даже если count объявлен как volatile. Для этого нужны AtomicInteger, synchronized или Lock.

Когда использовать volatile:

  • Простые флаги состояния (boolean flag).
  • Переменные, записываемые одним потоком и читаемые многими (например, конфигурация).
  • Объекты, публикуемые безопасным способом (safe publication) через volatile-ссылку.

Альтернативы для сложных сценариев: java.util.concurrent.atomic, synchronized, Lock.