Какую проблему решает ключевое слово volatile в Java?

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

Ответ

Ключевое слово volatile решает две основные проблемы в многопоточном программировании:

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

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

// БЕЗ volatile (поток #2 может никогда не увидеть изменение flag)
class ProblemExample {
    private boolean flag = false; // НЕ volatile
    public void writer() { flag = true; } // Поток #1
    public void reader() { while (!flag) { /* бесконечный цикл */ } } // Поток #2
}

// С volatile (гарантируется видимость изменений)
class SolutionExample {
    private volatile boolean flag = false;
    public void writer() { flag = true; } // Запись в volatile
    public void reader() { while (!flag) { /* цикл завершится */ } } // Чтение volatile
}

Ключевые свойства и ограничения:

  • Happens-before: Запись в volatile-переменную происходит-до (happens-before) любого последующего чтения этой же переменной.
  • НЕ гарантирует атомарность: volatile не делает составные операции (например, count++) атомарными. Для атомарных операций используйте AtomicInteger или синхронизацию.
  • Применение: Идеально подходит для флагов завершения, прерывания работы или однократной публикации неизменяемых объектов (safe publication).