volatile
volatile задает правила видимости и порядка для одного поля:
- запись в volatile-поле happens-before последующего чтения этого значения;
- компилятор и CPU не могут переупорядочить операции через эту границу вопреки JMM;
- чтение получает актуально опубликованное значение.
final class Worker implements Runnable {
private volatile boolean running = true;
void stop() {
running = false;
}
@Override
public void run() {
while (running) {
doOneIteration();
}
}
}Такой флаг подходит, только если цикл не зависает в непрерываемой блокирующей операции. Для sleep, queue или lock обычно нужен interrupt.
Чего volatile не дает
volatile int counter;
counter++; // read -> add -> write, не атомарноДва потока могут прочитать одинаковое значение и потерять одно увеличение. Для счетчика — AtomicInteger.incrementAndGet() или критическая секция.
volatile также не защищает инвариант нескольких полей:
volatile int lower;
volatile int upper; // lower <= upper не становится атомарнымСпецификация разрешает JVM разбивать чтение или запись не-volatile long/double на две 32-битные операции, хотя обычные современные JVM, как правило, этого не делают. Для volatile-полей эти чтения и записи гарантированно атомарны. Но атомарность одного присваивания не заменяет видимость, порядок и атомарность ++.
Безопасная публикация
Если поток сначала заполнил обычные поля объекта, затем записал ссылку или флаг в volatile, поток, прочитавший эту volatile-запись, увидит предшествующие записи.
См. Atomic типы данных, Race condition, Java Memory Model.
На собеседовании
- Атомарен ли
volatileCounter++? Нет. - Что дает volatile? Видимость и ordering/happens-before для чтения и записи поля.
- Достаточно ли volatile для связанного состояния двух полей? Нет.
- Атомарны ли обычные long/double по JLS? Для не-volatile спецификация допускает разделение; volatile-вариант гарантированно атомарен.
- Можно ли volatile-флагом остановить блокирующий
take()? Нет, поток должен проснуться; обычно interrupt.