JMM¶
JMM устанавливает правила использования потоками разделяемой памяти
Модели памяти процессоров¶
- Сильная модель
Все видят одинаковые значения.
- Слабая модель
Предпологает наличие барьеров памяти.
Flush - Инструкция на сброс данных (отчистка кэша (сброс) в основную память).
Инвалидация - объявление данных в кэше недействительными (значения впредь берутся из основной памяти).
Критическая секция¶
Критическая секция - участок кода, где используется доступ к общему ресурсу.
Не должно в критической секции быть более одного потока в один момент времени (~ или же могут быть несколько читателей).
Решает проблемы Race Condition.
Cache coherence protocol¶
Cache coherence - Это (применительно к многопроцессорным системам с кэш-памятью) свойство согласованности данных, вообще говоря разбросанных по различным кэшам. Очевидно, что если у нас одни и те же данные могут быть скэшированы в кэш-памяти разных уровней, и в локальных блоках кэш-памяти разных процессоров/ядер - при модификации этих данных легко может случиться, что существует множество несогласованных версий одних и тех же данных.
Производители процессоров обычно реализуют какой-либо аппаратный механизм поддержания согласованности данных различных кэшей между собой, и с основной памятью; когерентность кэша довольно тесно связана с аппаратной моделью памяти соответствующей системы.
- Подслушивание (snooping)
Есть общая шина, к которой подключены кэши всех ядер, и к ней же подключен контроллер основной памяти. Любые запросы отправляются бродкастом на шину, и видны всем подключенным к шине сразу.
The protocol CPUs use to transfer cache lines between main memory and other CPUs.
MSI¶
MSI - базовый Cache coherence protocol - работает в многопроцессорных системах. Как и в других протоколах когерентности кэша, абревиатура протокола идентифицируют возможные состояния, в которых может быть строка (cache line):
M (Modified) - строка была изменена в кеше. Данные в кэш-памяти теперь несовместимы с хранилищем (например, памятью). Кэш со строкой в состоянии «M» отвечает за запись строки в хранилище, когда она убирается из кэша (evicted), т.е. происходит flush.
S (Shared) - Эта строка не изменена и существует в состоянии только для чтения по крайней мере в одном кэше. Кэш может удалить данные, не записывая их в хранилище.
I (Invalid) Эта строка либо отсутствует в текущем кэше, либо была аннулирована запросом шины (напр. строка по этому же адресу перешла в состояние Modified. Инвалидация), и должена быть извлечена из памяти или другого кэша, если строка должна храниться в этом кэше.
Ссылки:
Fasle Sharing¶
Fasle Sharing - ситуация, при которой системы с реализованными протоколами когерентности кешей, ухудшают свою производительность, из-за того что не связанные переменные входят в один и тот же cache-line.
Note
cache-line имеет разный размер на разных процессорах – от 32 до 128 байт.
Ссылки:
Правила конструирования объектов¶
Если объект правильно построен (без утечек ссылок в конструкторе объекта и без исключений) - все потоки будут видеть корректное состояние его final полей
Объекты, достижимые через данные (инициализируемые) final поля так же будут гарантированно корректно отображаться для всех потоков. (например присвоение final полю ссылки на массив - все элементы массива будут независимо от потока одинаково видны без дополнительной синхронизации).
Но если после проставления в final поле ссылки - объект по этой ссылке будет изменен - нет гарантий на корректное отображение. Он может быть изменен даже в том же конструкторе:
data = new int[]{1,2}; data[1] = 10; // после правильного конструирования data != null, // НО (в других потоках) data[1] может быть == 2
Любое поле (в том числе final) может быть некорректно отображаемое, при публикации ссылки (досрочная публикация) на this в конструкторе (в т.ч. запись его в static поля), даже в текущем потоке, из-за перестановки.
Только после завершения конструктора можно гарантировать видимость проставленного в final поле значения Любой анонимный объект созднный в конструкторе имеет неявную ссылку на текущий, оборачивающий объект. Так же при запуске потока из конструктора присутствует досрочная публикация.
Сначала происходит построение объекта - потом присваивание ссылки на него.
Все значения, выставленне при объявлении статических полей или статическом инициализаторе выдны любому потоку, получившему доступ к этому классу, без какой либо синхронизации.
volatile¶
volatile - используется для данных, которые могут храниться только в heap (~~~)
Обращение не в кэш процессора за значением, а напрямую в память (visibility).
Кэш хранит копию часто используемых данных из RAM, обычно не больших блоков. Могут быть как из heap, так и из стека.
cashline - то что считывается в кэш при доступе к определенному участку памяти.
Атомарные операции чтения и записи для long и double (на 32 разрядных процессорах).
Переупорядочивание volatile переменных не происходит.
Volatile переменные являются memory барьерами, в отношении любой операции чтения, произошедшей после чтения volaile, и любой операции записи, до записи в volaile переменную.
a = 1; volatile = 2; read(volatile); read(a)Гарантируется Program Order:
\(setA[X] \Rightarrow setVolatile[X] \Rightarrow showVolatile[X] \Rightarrow showA[X]\)
Out-Of-Order Excecution (Внеочередное исполнение) машинных инструкций — исполнение машинных инструкций не в порядке следования в машинном коде (как было при выполнении инструкций по порядку (англ. in-order execution)), а в порядке готовности к выполнению. Реализуется с целью повышения производительности вычислительных устройств
Правила межпоточного взаимодействия. Happens-before¶
Отношение Happens-before (->):
\(Operation1[Thread1] \rightarrow Operation2[Thread2]\)Означает, что все изменения, выполненные Потоком1 до момента Операции1, а также изменения, которые повлекла эта операция - будут видны Потоку2 на Операции2 и после. Happens-before обеспечивает Flush кэша Потока1 по достижению Операции2
Happens-before обладает свойством транзитивности:
\(A[X] \rightarrow B[Y] \rightarrow C[Z] \sim A[X] \rightarrow C[Z]\)Отношение Program Order (=>):
\(A[X] \rightarrow B[X] \sim A[X] \Rightarrow B[X]\)Описывает порядок Program Order, который определяет отношение Happens-before только на явных зависимых элементах, которые нальзя переставить друг с другом:
Race Condition:
\(SetVal[X] \rightarrow \begin{Bmatrix} SetVal[Y] \Rightarrow ShowVal[Y] \\ SetVal[Z] \Rightarrow ShowVal[Z] \end{Bmatrix}\)
Освобождение монитора¶
(Освобождение монитора) hb (каждого последующего заполучения того же самого монитора)
Выведет myVal, при том, что поле может быть не volatile!, так как на каждом hb выполняется flush (~~~)
Выведет myVal
Все что случилось до hb и после (до выхода из монитора и после входа) - может восприниматься, как операции, происходящие в одном потоке с точки зрения JMM
То же самое, что и с захватом монитора
Операция записи в volatile-поле¶
(Операция записи в volatile-поле) hb (каждой последующей операции чтения того же самого поля)
выведет 1
Program Order (Happens-before) между записью в volatile поля гарантируется из-за модификатора volatile, а именно из-за того, что Volatile2 является таковым, т.е. memory барьером
выведет 1
Состояния потоков¶
как и в примерах выше с отношениями hb
Конструирование объекта¶
CAS¶
Обычный CAS имеет семантику volatile read+write. В частности, это означает, что между двумя CAS-ами одной переменной всегда существует ребро happens-before