Java Memory Model определяет правила взаимодействия потоков с памятью, устанавливая принципы видимости, порядка и атомарности операций. Он описывает, как изменения в памяти, сделанные одним потоком, становятся видимыми другим потокам.
Основные концепции JMM
a. Атомарность
- Описание: Операция является атомарной, если она выполняется как единое неделимое действие.
- Пример атомарных операций в Java:
- Чтение и запись примитивных типов данных размером до 32 бит (
int
,float
,boolean
,char
,short
,byte
). - Операции с
long
иdouble
не гарантированно атомарны в Java до версии 1.5, но начиная с Java 1.5, они стали атомарными.
- Чтение и запись примитивных типов данных размером до 32 бит (
b. Видимость
- Описание: Обеспечивает, что изменения, сделанные одним потоком, становятся видимыми другим потокам.
- Проблема видимости: Без правильной синхронизации один поток может не увидеть изменения, сделанные другим потоком.
c. Упорядочение (Ordering)
- Описание: Определяет порядок, в котором выполняются операции.
- Переупорядочивание инструкций: Компилятор и процессор могут менять порядок выполнения инструкций для оптимизации, что может привести к непредсказуемому поведению в многопоточной среде.
d. Отношение “Happens-Before”
- Описание: Модель, определяющая порядок видимости операций между потоками.
- Примеры “happens-before”:
- Блок
synchronized
: выход из синхронизированного блока одним потоком “happens-before” входа в тот же блок другим потоком. - Запись в переменную
volatile
“happens-before” последующего чтения этой переменной другим потоком.
- Блок
Механизмы синхронизации в JMM
a. volatile
- Гарантии:
- Видимость изменений между потоками.
- Запрет переупорядочивания операций с этой переменной.
- Использование: Для переменных, которые читаются и записываются несколькими потоками, и где операции над ними атомарны.
b. synchronized
- Гарантии:
- Эксклюзивный доступ к блоку кода или методу.
- Видимость и атомарность операций внутри синхронизированного блока.
- Использование: Когда требуется предотвратить состояние гонки и обеспечить атомарность составных операций.
c. final
- Гарантии: Объекты, на которые ссылаются
final
переменные, видны другим потокам после создания объекта. - Использование: Для неизменяемых объектов и обеспечения безопасной публикации объектов.
4. Проблемы многопоточности и JMM
a. Состояние гонки (Race Condition)
- Описание: Происходит, когда несколько потоков одновременно обращаются к общим данным, и результат зависит от порядка выполнения операций.
- Решение: Использование синхронизации для контроля доступа к общим ресурсам.
b. Видимость изменений
- Описание: Поток может не видеть изменения переменной, сделанные другим потоком.
- Решение: Использование
volatile
или синхронизации.
c. Переупорядочивание инструкций
- Описание: Компилятор или процессор может менять порядок выполнения инструкций, что может привести к неожиданному поведению.
- Решение: Использование
volatile
,synchronized
, которые устанавливают барьеры памяти.
5. Примеры и пояснения
Двойная проверка синхронизации (Double-Checked Locking)
- Проблема: В прошлом, из-за переупорядочивания инструкций, паттерн двойной проверки не работал корректно.
- Решение: Использование
volatile
с переменной-синглтоном.
6. Рекомендации для подготовки
- Изучите Java Memory Model (JSR-133): Понимание официальной спецификации поможет глубже понять принципы работы.
- Практикуйте написание многопоточных программ: Реализуйте примеры с использованием
volatile
,synchronized
,Locks
. - Изучите проблемы многопоточности: Понимайте причины и способы решения состояний гонки, проблем видимости и переупорядочивания.
- Ознакомьтесь с высокоуровневыми конструкциями из
java.util.concurrent
:ReentrantLock
,Semaphore
,CountDownLatch
,CyclicBarrier
,ConcurrentHashMap
и др.