Volatile: примеры (JAVA)

volatile в Java: видимость и упорядочение
Раздел: Многопоточность, Синхронизация
volatile

Описание volatile

volatile - это модификатор полей (переменных экземпляра или статических) в Java. Он гарантирует, что чтение и запись переменной происходят непосредственно из основной памяти, минуя кэш потока. Это обеспечивает видимость изменений для всех нитей и предотвращает переупорядочивание инструкций компилятором и процессором относительно других операций с volatile-переменными.

Ключевое слово не является функцией или методом, а используется в объявлении поля:

private volatile boolean running;

Когда используется: для флагов завершения работы, состояний готовности, сигнальных переменных и других случаев, когда требуется простой обмен данными между потоками без составных операций.

Аргументы: отсутствуют (применяется к полю). Возвращаемое значение: не применимо. Эффект: каждое чтение даёт актуальное значение, запись видна всем потокам немедленно.

Примеры использования volatile

Флаг завершения цикла

public class VolatileFlag {
    private static volatile boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!stop) {
                // работа потока
            }
            System.out.println("Поток остановлен");
        }).start();

        Thread.sleep(100);
        stop = true;
    }
}
Поток остановлен

Неатомарный счётчик (ошибочно)

private volatile int count;
public void increment() { count++; } // не атомарно
Результат непредсказуем при конкурентном доступе

Похожие средства в Java

synchronized - обеспечивает как видимость, так и атомарность. Используется, когда требуется блокировка и составные операции. AtomicInteger, AtomicBoolean и другие классы из java.util.concurrent.atomic - обеспечивают атомарные операции без блокировок, с использованием машинных инструкций CAS. Предпочтительны для счётчиков и накопления.

final - гарантирует видимость инициализированного значения после завершения конструктора без дополнительных средств. Lock - более гибкая блокировка, чем synchronized.

Выбор: volatile - для простых флагов; Atomic* - для атомарных обновлений; synchronized - для больших критических секций.

Аналоги в других языках

C# - ключевое слово volatile работает аналогично Java, но с некоторыми отличиями в модели памяти.

// C#
volatile bool stop = false;

Kotlin - аннотация @Volatile делает поле volatile.

@Volatile
var running = false

Go - нет прямого аналога; используются примитивы синхронизации (sync.Mutex, sync/atomic).

// Go
var stop int32
atomic.StoreInt32(&stop, 1)
for atomic.LoadInt32(&stop) == 0 {}

Python - нет volatile; для взаимодействия потоков используются threading.Event, Lock или queue.

# Python
from threading import Event
stop = Event()
while not stop.is_set(): ...

JavaScript - в однопоточном окружении не нужен; в SharedArrayBuffer используется Atomics.

// JavaScript (Web Workers)
const sab = new SharedArrayBuffer(4);
const view = new Int32Array(sab);
Atomics.store(view, 0, 1);
Atomics.load(view, 0);

Типичные ошибки

Использование volatile для составных операций

private volatile int count;
public void increment() { count++; } // не атомарно!
Результат: потерянные обновления

Убеждение, что volatile делает класс потокобезопасным

public class Config {
    private volatile int x;
    private volatile int y;
    // нет синхронизации для инварианта x < y
}
Нарушение инварианта

Применение к локальным переменным

void method() {
    volatile int local; // ошибка компиляции
}
Ошибка компиляции: volatile не допускается для локальных переменных

Изменения в версиях

До Java 5 (JSR 133) модель памяти была слабее, volatile не гарантировал упорядочение относительно не-volatile переменных. Начиная с Java 5 volatile обеспечивает happens-before и полное упорядочение (total order) для записей и чтений. В последующих версиях (Java 8, 9, 11, 17) семантика не менялась, но улучшена производительность на некоторых архитектурах.

Расширенные примеры

Двойная проверка блокировки (Double-checked locking)

Пример java
public class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
Без volatile возможна частичная инициализация (до Java 5)

volatile для массивов (ссылка на массив)

Пример java
private volatile int[] array;

public void updateArray(int[] newArray) {
    array = newArray; // видимость обновления ссылки
}
public int readElement(int idx) {
    return array[idx]; // чтение ссылки volatile, затем чтение элемента (не volatile)
}
Гарантируется видимость новой ссылки на массив, но не содержимого массива

volatile long/double

Пример java
private volatile long bigValue;

// Запись 64-битного значения атомарна с volatile
bigValue = 123456789012345L;
Без volatile запись long может быть разделена на два 32-битных

Использование в лямбда-выражениях

Пример java
private volatile boolean done;
public void process() {
    Runnable task = () -> {
        while (!done) { /* работа */ }
    };
    new Thread(task).start();
}
Лямбда захватывает ссылку на this, и volatile обеспечивает видимость флага

джава volatile function comments

En
Volatile Ключевое слово для переменных, хранящихся в основной памяти