История вопроса:
Когда Java только появлялась, формального описания того, как память и потоки взаимодействуют между собой, не существовало. В результате те же самые программы могли вести себя по-разному на разных JVM, что приводило к трудноуловимым багам. В 2004 году в спецификацию Java была добавлена Java Memory Model (JMM), призванная строго определить, как потоки видят обновления переменных.
Проблема:
Без четкой модели памяти операции записи и чтения могут быть перемешаны компилятором или процессором, что приводит к неожиданному поведению при доступе к общим переменным из разных потоков. Это может вызвать race condition, visibility issues и сложные для отладки ошибки синхронизации.
Решение:
JMM определяет правила: когда изменения, внесенные одним потоком, становятся видимыми другим. Она устанавливает понятия happens-before, синхронизаторов (synchronized, volatile, final), внутренних блокировок и ограничивает реорганизацию инструкций в многопоточном окружении.
Пример кода:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int get() { return count; } }
Ключевые особенности:
Почему volatile не делает операции атомарными, а только обеспечивает видимость?
Volatile гарантирует только видимость изменений между потоками, но не атомарность изменения. Например, инкрементирование volatile переменной не является атомарной операцией, поскольку само действие состоит из считывания, изменения и записи.
Пример кода:
volatile int count = 0; count++; // Не атомарно!
Может ли synchronized блок гарантировать видимость изменений за его пределами?
Да. После выхода из synchronized блока все изменения, сделанные внутри, видимы другим потокам, которые войдут в блок на тот же объект-монитор.
Когда изменения final-полей становятся видимыми другим потокам?
Final поля гарантируют корректную публикацию только если объект полностью сконструирован до передачи ссылок на него другим потокам, например, через immutable объекты. Иначе возможна видимость неинициализированного состояния.
Негативный кейс
Разработчик решил увеличить счетчик входов на сайт с помощью simple volatile инкремента из нескольких потоков
Плюсы:
Минусы:
Позитивный кейс
Использовали AtomicInteger для счетчика или synchronized методы
Плюсы:
Минусы: