Получить доступ

Java Memory Model: happens-before и volatile

Зачем нужна модель памяти, видимость и переупорядочивание, отношение happens-before, семантика volatile и final, гонки данных.

Зачем нужна модель памяти

На многоядерных процессорах каждый поток может работать с кэшированными версиями данных, а компилятор и процессор вправе переупорядочивать инструкции ради скорости. Без правил один поток мог бы не увидеть запись другого или увидеть её «не в том порядке».

Java Memory Model (JMM) — это контракт, который определяет, какие записи одного потока гарантированно видны другому и в каком порядке. Он абстрагирует железо: пишете по правилам JMM — код корректен на любой платформе.

Три проблемы: видимость, упорядочивание, атомарность

Видимость: увидит ли поток B значение, записанное потоком A. Упорядочивание: в каком порядке B видит несколько записей A. Атомарность: выполняется ли операция неделимо (например, i++ — это read-modify-write, не атомарно).

Это три РАЗНЫХ свойства, и их часто путают. volatile решает видимость и упорядочивание, но НЕ делает составные операции атомарными.

Отношение happens-before

JMM формулируется через happens-before: если действие A happens-before B, то эффект A виден B. Основные источники: порядок внутри одного потока (program order); разблокировка монитора happens-before его последующего захвата; запись volatile happens-before последующего чтения той же переменной; Thread.start() happens-before кода потока; код потока happens-before возврата из join().

Если между двумя обращениями к общим данным нет цепочки happens-before — это гонка данных, и поведение не гарантировано.

Две таймлинии потоков: запись volatile в потоке A связана стрелкой happens-before с чтением volatile в потоке B, что делает все предыдущие записи A видимыми для B.
happens-before: запись volatile в одном потоке делает предшествующие ей записи видимыми для читающего потока.

Семантика volatile и final

Чтение/запись volatile-поля атомарны (включая long/double) и устанавливают happens-before: всё, что поток записал до записи volatile, становится видно потоку, который затем прочитал это volatile. Поэтому volatile годится для флагов и публикации ссылок, но не для счётчиков (count++ — не атомарно; нужны Atomic-классы или synchronized).

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

volatile boolean running = true; // флаг остановки — корректно
void stop() { running = false; }  // видно другому потоку
// но: volatile int n; n++; — НЕ атомарно (read-modify-write)

Как отвечать на собеседовании

Разделите видимость/упорядочивание/атомарность, объясните happens-before и его источники (volatile, monitor lock, start/join), и точно сформулируйте volatile: даёт видимость и запрет переупорядочивания, но не атомарность составных операций (для счётчиков — Atomic/synchronized). Добавьте про безопасную публикацию через final — это уровень, на котором видно глубокое понимание конкурентности.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем