Из каких основных концепций состоит Java Memory Model (JMM)?

«Из каких основных концепций состоит Java Memory Model (JMM)?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

  1. Happens-before (Происходит-до): Фундаментальное отношение порядка. Если операция A happens-before операции B, то все изменения памяти, сделанные в A, видны для B.

    • Правила: Запуск потока, захват/освобождение монитора (synchronized), запись/чтение volatile, инициализация объекта.
  2. Видимость (Visibility): Гарантирует, что изменения переменной, сделанные одним потоком, становятся видимыми другим потокам. Без синхронизации это не гарантируется из-за кэшей процессора.

  3. Атомарность (Atomicity): Гарантирует, что операция выполняется как единое целое. В Java атомарны:

    • Чтение/запись примитивов (кроме long и double без volatile).
    • Операции с volatile переменными.
    • Операции с классами из java.util.concurrent.atomic.
  4. Переупорядочивание (Reordering): Компилятор и процессор могут оптимизировать код, меняя порядок инструкций, если это не нарушает семантику в рамках одного потока. JMM определяет барьеры, которые ограничивают такое переупорядочивание между потоками.

Практические инструменты, обеспечивающие согласованность по JMM:

  • synchronized блоки/методы: создают отношения happens-before и гарантируют атомарность блока.
  • volatile переменные: гарантируют видимость и запрещают переупорядочивание операций с ними.
  • final поля: гарантируют видимость корректно проинициализированного объекта после его конструирования.
  • Классы из java.util.concurrent (AtomicInteger, ConcurrentHashMap и др.).

Пример проблемы видимости и её решения:

// ПРОБЛЕМА: Без синхронизации поток reader может никогда не увидеть флаг = true.
class Problem {
    boolean flag = false; // НЕ volatile
    void writer() { flag = true; }
    void reader() { while (!flag) { /* бесконечный цикл */ } }
}

// РЕШЕНИЕ 1: Использование volatile
class Solution1 {
    volatile boolean flag = false; // Гарантирует видимость и happens-before
}

// РЕШЕНИЕ 2: Использование synchronized
class Solution2 {
    boolean flag = false;
    synchronized void writer() { flag = true; }
    synchronized boolean reader() { return flag; }
}