← Назад к вопросам

Расскажите о модели памяти Java (JMM).

2.0 Middle🔥 201 комментариев
#JVM и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Модель памяти Java (Java Memory Model, JMM)

Java Memory Model — это спецификация, которая определяет, как потоки взаимодействуют через память. JMM гарантирует, что даже в многопоточной среде поведение программы остаётся предсказуемым. Это один из самых сложных, но критически важных концептов для написания корректного многопоточного кода.

Проблема: Видимость и переупорядочение

Без JMM возникли бы проблемы:

public class MemoryVisibilityProblem {
    private int x = 0;
    private boolean ready = false;
    
    public void thread1() {
        x = 42;
        ready = true; // Может выполниться раньше x = 42!
    }
    
    public void thread2() {
        if (ready) {
            System.out.println(x); // Может вывести 0 вместо 42
        }
    }
}

Три главные проблемы:

  1. Видимость (Visibility) — изменения в одном потоке невидимы другому
  2. Переупорядочение (Reordering) — инструкции выполняются не в том порядке
  3. Атомарность (Atomicity) — операции прерываются посередине

Архитектура памяти Java

┌─────────────────────────────────────┐
│      Shared Heap (Главная память)   │
│  (видна всем потокам)               │
│  - Объекты                          │
│  - Массивы                          │
└──────────┬──────────────────────────┘
           │
    ┌──────┴──────┬──────────┬────────┐
    │             │          │        │
┌───▼───┐   ┌────▼──┐  ┌────▼──┐ ┌──▼────┐
│Thread1│   │Thread2│  │Thread3│ │ThreadN│
│ Cache │   │ Cache │  │ Cache │ │ Cache │
│(local)│   │(local)│  │(local)│ │(local)│
│ Stack │   │ Stack │  │ Stack │ │ Stack │
└───────┘   └───────┘  └───────┘ └───────┘

Каждый поток имеет свой:

  • Локальный кэш — для быстрого доступа
  • Стек — для локальных переменных
  • Регистры CPU — для промежуточных вычислений

Гарантии JMM

1. Гарантия видимости (Visibility Guarantee)

Изменения одного потока должны быть видны другому при правильном использовании синхронизации.

public class VisibilityGuarantee {
    private volatile int sharedValue = 0;
    
    public void thread1Work() {
        sharedValue = 42;
        System.out.println("Установил значение 42");
    }
    
    public void thread2Work() {
        int value = sharedValue;
        System.out.println("Прочитал значение: " + value); // Гарантированно 42
    }
}

2. Гарантия переупорядочения (Ordering Guarantee)

Чтение и запись памяти не могут быть произвольно переупорядочены.

public class ReorderingExample {
    private int x = 0;
    private int y = 0;
    
    public void thread1() {
        x = 1;  // Инструкция 1
        int r1 = y;  // Инструкция 2
        System.out.println("r1 = " + r1);
    }
    
    public void thread2() {
        y = 1;  // Инструкция 3
        int r2 = x;  // Инструкция 4
        System.out.println("r2 = " + r2);
    }
    
    // BEЗ JMM гарантии: обе потока могут вывести 0
    // С JMM: один из потоков обязательно выведет 1
}

Happens-Before отношение

Happens-before — это ключевое понятие JMM. Если действие A happens-before B, то все эффекты A видны при выполнении B.

Основные правила happens-before:

1. Program Order Rule

public class ProgramOrder {
    public void method() {
        int x = 1;  // Действие 1
        int y = 2;  // Действие 2
        int z = x + y;  // Гарантированно видит x=1, y=2
    }
}

2. Synchronization Order Rule (synchronized блоки)

public class SynchronizationOrder {
    private int sharedValue = 0;
    
    public synchronized void write() {
        sharedValue = 42; // Действие 1
    }
    
    public synchronized void read() {
        int value = sharedValue; // Действие 2
        // Гарантированно видит sharedValue = 42
    }
}

3. Volatile Variable Rules

public class VolatileOrder {
    private volatile int flag = 0;
    
    public void thread1() {
        flag = 1; // happens-before read в thread2
    }
    
    public void thread2() {
        while (flag == 0) { // Видит изменение из thread1
            // Ждём
        }
    }
}

4. Thread Start Rule

public class ThreadStart {
    private int x = 0;
    
    public void main() {
        x = 42;
        Thread t = new Thread(() -> {
            int value = x; // Гарантированно видит 42
        });
        t.start(); // Все действия перед start видны новому потоку
    }
}

5. Thread Termination Rule

public class ThreadTermination {
    private int x = 0;
    
    public void main() throws InterruptedException {
        Thread t = new Thread(() -> {
            x = 42; // Изменение x
        });
        t.start();
        t.join(); // Все действия в потоке видны после join
        int value = x; // Гарантированно видит 42
    }
}

Операции с памятью

JMM определяет четыре типа атомарных операций:

public class MemoryOperations {
    private int value = 0;
    
    // 1. Read (чтение из памяти)
    public void read() {
        int x = value; // Читаем значение из памяти
    }
    
    // 2. Write (запись в память)
    public void write() {
        value = 42; // Записываем значение в память
    }
    
    // 3. Lock (получение монитора)
    public synchronized void lockAction() {
        // Получение монитора объекта
    }
    
    // 4. Unlock (освобождение монитора)
    // Происходит при выходе из synchronized блока
}

Барьеры памяти (Memory Barriers)

Memory barriers — это инструкции процессора, которые предотвращают переупорядочение операций с памятью.

public class MemoryBarriers {
    private volatile int x = 0;
    private int y = 0;
    
    public void write() {
        y = 1;
        x = 1; // Volatile write действует как Write Barrier
    }
    
    public void read() {
        int a = x; // Volatile read действует как Read Barrier
        int b = y; // Гарантированно видит y = 1
    }
}

Типы барьеров:

  • Write Barrier — предотвращает перемещение записей через барьер
  • Read Barrier — предотвращает перемещение чтений через барьер
  • Full Barrier — предотвращает перемещение операций в обе стороны

Практический пример: Правильная и неправильная синхронизация

public class IncorrectSynchronization {
    private int count = 0;
    
    // ❌ НЕПРАВИЛЬНО - race condition
    public void incrementBad() {
        count++; // НЕ атомарно: read + modify + write
    }
    
    // ✅ ПРАВИЛЬНО - с synchronized
    public synchronized void incrementGood() {
        count++; // Атомарная операция
    }
}

public class CorrectSynchronization {
    private int count = 0;
    
    // ✅ ПРАВИЛЬНО - с volatile (для простого флага)
    private volatile boolean running = true;
    
    public void stopWork() {
        running = false; // Видимо всем потокам
    }
    
    public void work() {
        while (running) {
            // Работа
        }
    }
}

Проблемы без правильной JMM

1. Невидимые обновления (Invisible Updates)

public class InvisibleUpdates {
    private int x = 0;
    
    public void thread1() {
        x = 42; // Может остаться в кэше потока
    }
    
    public void thread2() {
        while (x == 0) { // Может никогда не увидеть 42
            // Бесконечный цикл!
        }
    }
}

2. Переупорядочение инструкций (Instruction Reordering)

public class InstructionReordering {
    private int x = 0, y = 0;
    private boolean flag = false;
    
    public void thread1() {
        x = 1;
        y = 2;
        flag = true; // Может выполниться ДО x = 1 и y = 2!
    }
    
    public void thread2() {
        if (flag) {
            int r = x + y; // Может быть 0, 1, 2, или 3!
        }
    }
}

Инструменты для анализа

Java Concurrency в действии:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CyclicBarrier;

public class MemoryModelDemo {
    private static volatile int volatileValue = 0;
    private static AtomicInteger atomicValue = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        // volatile гарантирует видимость
        volatileValue = 42;
        
        // AtomicInteger гарантирует атомарность
        atomicValue.incrementAndGet();
        
        // CyclicBarrier для синхронизации потоков
        CyclicBarrier barrier = new CyclicBarrier(2);
    }
}

JMM гарантии на практике

МеханизмВидимостьПереупорядочениеАтомарность
volatile✓ Да✓ Частично✗ Нет
synchronized✓ Да✓ Да✓ Да
AtomicXxx✓ Да✓ Да✓ Да
final✓ Да*✓ Да✗ Нет
plain fields✗ Нет✗ Нет✗ Нет

*final гарантирует видимость только при конструировании

Ключевые выводы

  1. JMM гарантирует предсказуемость в многопоточной среде
  2. Happens-before отношение — основа всех гарантий
  3. volatile — для видимости простых переменных
  4. synchronized — для взаимного исключения и видимости
  5. Atomic классы — для атомарных операций
  6. Без синхронизации — нет гарантий!
  7. Всегда предполагай худшее — не полагайся на порядок выполнения
Расскажите о модели памяти Java (JMM). | PrepBro