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

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

Ответ

Ключевое слово volatile решает проблемы видимости и упорядочивания операций с переменной в многопоточных программах, но не гарантирует атомарность.

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

  1. Видимость (Visibility): Любое изменение volatile-переменной одним потоком немедленно становится видно всем другим потокам. Без volatile изменения могут остаться в кэше процессора и не быть замеченными.
  2. Запрет переупорядочивания (Happens-before): Операции чтения/записи volatile-переменной создают барьер памяти. Операции до записи не могут быть переупорядочены после нее, а операции после чтения — не могут быть переупорядочены до него.

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

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

Пример правильного использования:

public class TaskRunner {
    // Флаг для безопасной остановки потока извне
    private volatile boolean running = true;

    public void stop() {
        running = false; // Изменение видно рабочему потоку сразу
    }

    public void run() {
        while (running) { // Чтение всегда актуального значения
            // Выполнение задачи...
        }
    }
}

Пример НЕправильного использования (отсутствие атомарности):

public class Counter {
    private volatile int count = 0;

    public void increment() {
        count++; // НЕПРАВИЛЬНО! Операция не атомарна.
        // Поток A: читает count (0)
        // Поток B: читает count (0)
        // Оба увеличивают значение до 1 и записывают.
        // Итог: после двух вызовов count = 1, а не 2.
    }
}
// Правильное решение: использовать java.util.concurrent.atomic.AtomicInteger

Вывод: Используйте volatile для простых флагов или переменных состояния, где операция записи атомарна сама по себе (простое присваивание). Для счетчиков или сложных состояний применяйте Atomic-классы или synchronized.