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 = falseGo - нет прямого аналога; используются примитивы синхронизации (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)
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 для массивов (ссылка на массив)
private volatile int[] array;
public void updateArray(int[] newArray) {
array = newArray; // видимость обновления ссылки
}
public int readElement(int idx) {
return array[idx]; // чтение ссылки volatile, затем чтение элемента (не volatile)
}Гарантируется видимость новой ссылки на массив, но не содержимого массива
volatile long/double
private volatile long bigValue;
// Запись 64-битного значения атомарна с volatile
bigValue = 123456789012345L;Без volatile запись long может быть разделена на два 32-битных
Использование в лямбда-выражениях
private volatile boolean done;
public void process() {
Runnable task = () -> {
while (!done) { /* работа */ }
};
new Thread(task).start();
}Лямбда захватывает ссылку на this, и volatile обеспечивает видимость флага