Java Memory Model

Java Memory Model (JMM) определяет, какие записи один поток может наблюдать в другом и какие переупорядочивания допустимы. Это спецификация поведения операций, а не схема heap, stack или кэшей процессора. Runtime-память описана отдельно в Стек и куча в Java.

Три проблемы общего состояния

СвойствоВопрос
АтомарностьМожет ли другой поток увидеть частично выполненную операцию?
ВидимостьКогда запись одного потока станет доступна другому?
ПорядокМожно ли наблюдать операции в ином порядке?

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

final class Worker {
    private int answer;
    private volatile boolean ready;
 
    void publish() {
        answer = 42;
        ready = true;
    }
 
    int await() {
        while (!ready) Thread.onSpinWait();
        return answer; // увидит 42 благодаря volatile-связи
    }
}

Запись ready = true имеет release-семантику, последующее чтение true — acquire-семантику. Через отношение happens before видна и предшествующая запись answer.

Data race

Data race существует, когда два потока обращаются к одной переменной, хотя бы одно обращение — запись, а действия не упорядочены happens-before. Программа без data races ведёт себя так, будто действия потоков образуют некоторое последовательное исполнение.

volatile обеспечивает видимость и порядок для конкретной переменной, но не делает составную операцию атомарной. count++ остаётся чтением, сложением и записью. Для него нужны блокировка или Atomic типы данных.

Safe publication

Объект безопасно публикуется через lock, volatile-ссылку, static initialization, потокобезопасную коллекцию или другие happens-before границы. Для корректно сконструированного объекта final-поля получают дополнительные гарантии, если this не утёк из конструктора.

Вопросы для собеседования

  1. JMM описывает heap и stack? Нет, она задаёт допустимые наблюдения операций.
  2. Что такое data race? Конфликтующие обращения без happens-before.
  3. Делает ли volatile i++ атомарным? Нет.
  4. Зачем safe publication? Чтобы другой поток увидел корректно сконструированное состояние.
  5. Может ли JIT менять порядок операций? Да, если не нарушает однопоточную семантику и ограничения JMM.
  6. Что даёт synchronization? Взаимное исключение плюс гарантии видимости через monitor happens-before.