Ответ
Java Memory Model (JMM) — это формальная спецификация, которая определяет, как потоки взаимодействуют через общую память. Она гарантирует предсказуемость поведения программы в многопроцессорных и многопоточных средах, где компилятор и процессор могут переупорядочивать инструкции для оптимизации.
Основные проблемы, которые решает JMM:
- Видимость изменений: Изменение переменной, сделанное в одном потоке, может быть не сразу видно другому потоку из-за кешей процессора.
- Переупорядочивание операций: Компилятор и CPU могут менять порядок независимых инструкций, что может привести к неожиданным результатам.
Ключевые концепции JMM:
- Happens-before: Отношение порядка между операциями. Если операция A happens-before операции B, то все изменения памяти, сделанные в A, гарантированно видны для B.
- Синхронизированные блоки (
synchronized): Создают отношения happens-before. Вход в монитор (начало блока) happens-before всему последующему входу в тот же монитор. volatileпеременные: Чтение volatile-поля happens-before любого последующего чтения того же поля. Запись в volatile-поле happens-before любого последующего чтения. Гарантирует видимость изменений и запрещает переупорядочивание операций с volatile-доступами.- Запуск и завершение потоков: Запуск потока (
Thread.start()) happens-before первым действием в этом потоке. Завершение потока happens-before успешному возврату изThread.join().
Пример проблемы (гонка данных) и решение:
// ПРОБЛЕМА: Гонка данных (Race Condition)
class UnsafeCounter {
private int count = 0; // Не volatile, не synchronized
public void increment() { count++; } // Неатомарная операция!
public int getCount() { return count; }
}
// В многопоточной среде два потока могут одновременно прочитать старое значение,
// увеличить его и записать обратно, потеряв одно из увеличений.
// РЕШЕНИЕ 1: Использование synchronized (создает happens-before)
class SafeCounter1 {
private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }
}
// РЕШЕНИЕ 2: Использование AtomicInteger (использует low-level CAS)
import java.util.concurrent.atomic.AtomicInteger;
class SafeCounter2 {
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }
public int getCount() { return count.get(); }
}
// РЕШЕНИЕ 3: Использование volatile (только если операция атомарна сама по себе)
class SafeFlag {
private volatile boolean stopped = false; // Запись и чтение атомарны для boolean
public void stop() { stopped = true; }
public boolean isStopped() { return stopped; }
}
Без понимания JMM невозможно писать корректные и эффективные многопоточные приложения.