Короткий ответ
Модель памяти Java — это набор формальных правил и гарантий, определяющих, как потоки взаимодействуют через память и какие результаты чтения и записи они обязаны «видеть», чтобы программа вела себя предсказуемо на разных процессорах и операционных системах. Центральное понятие — отношение happens-before: если действие A happens-before действия B, то результат A гарантированно виден действию B, и A не будет переупорядочено после B.
Как это работает подробнее
Без JMM компилятор, JIT и процессор могли бы свободно переупорядочивать операции и кэшировать значения, из-за чего поведение многопоточной программы зависело бы от конкретного железа. JMM определяет, какие переупорядочивания запрещены и какие гарантии видимости даёт Java.
Основные правила happens-before: порядок действий внутри одного потока (program order); освобождение монитора happens-before последующий захват того же монитора другим потоком; запись в volatile-переменную happens-before последующее чтение той же переменной другим потоком; вызов Thread.start() happens-before начала работы нового потока; завершение потока happens-before возврата из join().
- Отношение happens-before транзитивно: если A happens-before B, а B happens-before C, то A happens-before C.
- Если между действиями потоков нет happens-before, программа не вправе рассчитывать на актуальность значений и порядок операций — результат может отличаться от запуска к запуску.
Что использовать на практике
- Синхронизируйте доступ к общим данным через synchronized, volatile или классы java.util.concurrent — именно они строят отношения happens-before.
- Используйте volatile для простых флагов и публикации ссылок, где нужна только видимость, но не атомарность составных операций.
- Опирайтесь на формальные правила JMM, а не на «счастливо работающий» код без синхронизации.