Глубокое понимание модели памяти jvm-java (в сочетании с volatile)

1. Структура

Вставьте описание изображения здесь

  • Все переменные хранятся в основной памяти
  • Каждый поток имеет свою рабочую память
  • Рабочая память потока содержит копию основной памяти переменных, используемых потоком.
  • Все операции с переменными по потокам должны выполняться в рабочей памяти и не должны напрямую манипулировать основной памятью.
  • Передача переменных между потоками должна осуществляться в интерактивном режиме основной памятью.

Вставьте описание изображения здесь

2. Интерактивные атомарные операции между памятью

Lock (блокировка): переменная, которая действует на основную память, она идентифицирует переменную как исключительное состояние потока.

Разблокировать (unlock): переменная, которая действует на основную память, освобождает переменную, которая находится в заблокированном состоянии, и освобожденная переменная может быть заблокирована другими потоками.

· Чтение (чтение): переменная, которая воздействует на основную память, она передает значение переменной из основной памяти в рабочую память потока для последующих действий загрузки.

· Load (load): действует на переменные рабочей памяти, она помещает значение переменной, полученной из основной памяти операцией чтения, в переменную копию рабочей памяти.

Использование (использование): переменная, которая воздействует на рабочую память, передает значение переменной в рабочей памяти механизму выполнения, и виртуальная машина будет выполнять эту операцию всякий раз, когда она сталкивается с инструкцией байт-кода, которая должна использовать значение переменной.

Назначение (присваивание): переменная, которая воздействует на рабочую память. Она присваивает значение, полученное от исполнительного механизма, переменной рабочей памяти. Эта операция выполняется всякий раз, когда виртуальная машина встречает инструкцию байт-кода, которая присваивает значение переменной.

· Store (хранилище): переменная, которая воздействует на рабочую память, передает значение переменной в рабочей памяти в основную память для использования в последующих операциях записи.

· Запись (запись): действует на переменные основной памяти, помещает значения переменных, полученных из рабочей памяти операцией сохранения, в переменные основной памяти.

Если вы хотите скопировать переменную из основной памяти в рабочую память, вы должны выполнить операции чтения и загрузки по порядку. Если вы хотите синхронизировать переменные из рабочей памяти обратно в основную память, вы должны выполнить операции сохранения и записи по порядку. Обратите внимание, что модель памяти Java требует только того, чтобы две вышеуказанные операции выполнялись последовательно , но не требует непрерывного выполнения.

3. изменчивое понимание

  • Обеспечьте видимость этой переменной для всех потоков (как только поток изменяет значение, новое значение может быть немедленно известно другим потокам) (безопасность потока не гарантируется, например: ABA 2 1 3 и т. Д.)
  • Запретить изменение порядка команд

Переменные, измененные с помощью volatile, имеют следующие свойства:

❑ Видимость. При чтении переменной переменной всегда можно увидеть (любой поток) последнюю запись в переменную переменной.

❑ атомарность: чтение / запись любой отдельной переменной volatile является атомарной , но составная операция, аналогичная volatile ++, не является атомарной. (Например, длинный, двойной)

Переупорядочение (также понимаю чтение после записи, запись после чтения, запись после записи)

Что такое переупорядочение: для повышения производительности компиляторы и процессоры часто переупорядочивают команды в заданном порядке выполнения кода.
Причина: хорошая модель памяти фактически ослабит ограничения на правила процессора и компилятора, а это означает, что и программная технология, и аппаратная технология борются за одну и ту же цель
: не изменяя результаты выполнения программы, насколько это возможно. Улучшить эффективность исполнения. JMM минимизирует ограничения на нижний слой, чтобы он мог использовать свои
преимущества. Поэтому для повышения производительности при выполнении программ компиляторы и процессоры часто переупорядочивают инструкции. Общее переупорядочение можно разделить на следующие три
типа:
1. Оптимизированное по компилятору переупорядочение. Компилятор может изменить порядок выполнения операторов без изменения семантики однопоточной программы
2. Параллельное переупорядочение на уровне команд. Современные процессоры используют параллельную технологию на уровне команд для перекрытия нескольких команд. Если нет зависимости от данных, процессор
может изменить порядок выполнения машинных инструкций, соответствующих оператору
3. Переупорядочение системы памяти. Поскольку процессор использует буферы кэша и чтения / записи, это делает операции загрузки и сохранения не выполненными.
Вставьте описание изображения здесь

Вставьте описание изображения здесь
Если вы не используете изменчивую модификацию при определении инициализированной переменной, последний код «initialized = true» в потоке A может быть выполнен заранее из-за оптимизации переупорядочения команд (хотя Java используется как псевдокод, но тяжелый Оптимизация сортировки является операцией оптимизации на уровне машины, и раннее выполнение означает, что код сборки, соответствующий этому выражению, выполняется заранее), так что код, который использует информацию о конфигурации в потоке B, может иметь ошибки, и ключевое слово volatile может избежать таких ситуаций. вхождение

Есть еще один пример:
Вставьте описание изображения здесь

Там может быть i = 0, j = 0

Причина:
здесь поток 1 и поток 2 могут одновременно записывать общие переменные в свой собственный буфер записи (A1, B1), затем читать из памяти другую разделяемую переменную (A2, B2) и, наконец, записывать себя в буфер Сохраненные грязные данные обновляются в памяти (A3, B3). При выполнении в этой временной последовательности программа может получить результат x = y = 0.
Вставьте описание изображения здесь


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

4. Для 64-битных базовых типов long и double

Если несколько потоков совместно используют переменную типа long или double, которая не объявлена ​​как volatile, и одновременно считывают и изменяют их, то некоторые потоки могут читать переменную, которая не является ни исходным значением, ни другими потоками. Измененное значение представляет собой значение «половинной переменной». Но этот вид чтения "половинной переменной" очень редко

5. Бывает-до

Это очень полезный метод для оценки конкуренции данных и безопасности потока.

Операция, «выполняющаяся первой во времени», не означает, что операция будет «происходить первой». Так что, если операция «происходит первой», можно ли сделать вывод, что эта операция должна быть «первой по времени»? К сожалению, этот вывод также несостоятельный.

Между этими двумя операциями существует взаимосвязь «происходит до», которая не означает, что предыдущая операция должна быть выполнена перед следующей операцией! «случай-до» требует только, чтобы предыдущая операция (результат выполнения) была видна последней операции , а предыдущая операция ранжируется перед второй операцией (первая видна и упорядочена перед второй).

В принципе, нет причинно-следственной связи между временной последовательностью и принципом предшествующего возникновения, поэтому, когда мы измеряем параллельные проблемы безопасности, нас не должна беспокоить временная последовательность: все должно основываться на принципе предшествующего возникновения.

i = 1;       //线程A执行
j = i ;      //线程B执行

J равен 1? Предположим, что операция потока A (i = 1) происходит до операции потока B (j = i).
Затем можно определить, что j = 1 должно быть установлено после выполнения потока B.
Если у них нет принципа «происходит раньше», то j = 1 может быть неверным.

(Даже если код сначала выполняет j = 1, а затем выполняет j = i, это не обязательно j = 1, в основном зависит от того, встречается ли он раньше)


Вот некоторые «естественные» упреждающие отношения в модели памяти Java

Правило упорядочения программы: В потоке, в соответствии с порядком потока управления, операция, записанная спереди, выполняется до операции, записанной сзади. Обратите внимание, что порядок потока управления не является порядком программного кода, поскольку следует учитывать структуру ветвей и циклов.

· Правило блокировки монитора. Операция разблокировки выполняется до операции блокировки той же самой блокировки . Здесь следует подчеркнуть «тот же замок», а «сзади» относится к хронологическому порядку.

· Правило изменчивой переменной: операция записи в изменчивую переменную выполняется первой в последующей операции чтения этой переменной . Здесь «назад» также относится к хронологическому порядку.

Правило запуска потока (Правило запуска потока): метод start () объекта Thread сначала выполняется при каждом действии этого потока.

· Правило завершения потока (Правило завершения потока): все операции в потоке сначала выполняются при обнаружении завершения этого потока, мы можем передать метод Thread :: join () в конец, возвращаемое значение Thread :: isAlive () и другие средства. Проверьте, завершил ли поток выполнение.

Правило прерывания потока (Правило прерывания потока): вызов метода interrupt () потока происходит до того, как код прерванного потока обнаруживает возникновение события прерывания. Вы можете определить, происходит ли прерывание с помощью метода Thread :: interrupted ().

Правило финализации объекта (Finalizer Rule): Инициализация объекта (завершение выполнения конструктора) сначала происходит в начале его метода finalize ().

· Транзитивность: если операция A происходит до операции B, а операция B происходит до операции C, то она может быть завершена

На основе летучих случается, прежде чем

Вставьте описание изображения здесь
1) Согласно правилам последовательности программ, 1 происходит до 2, 3 происходит до 4.

2) По волатильному правилу 2 случается - до 3.

3) В соответствии с переходным правилом «происходит до», 1 происходит - до 4.

Переключение состояния потока 6.java

Вставьте описание изображения здесь
· Заблокировано: поток заблокирован. Разница между «заблокированным состоянием» и «состоянием ожидания» заключается в том, что «состояние блокировки» ожидает получения исключительной блокировки. Это событие произойдет, когда другой поток откажется от блокировки ; «Состояние ожидания» ожидает некоторый период времени, или происходит действие пробуждения. Когда программа ожидает входа в область синхронизации, поток переходит в это состояние.
Вставьте описание изображения здесь

7. Семантика памяти на основе энергозависимости

  • Написание семантики: При записи изменчивой переменной JMM обновит значение общей переменной в локальной памяти, соответствующей потоку, в основную память.
  • Семантика чтения: при чтении энергозависимой переменной JMM сделает недействительной локальную память, соответствующую потоку. Затем поток будет читать общую переменную из основной памяти.

Реализация семантики энергозависимой памяти (реализация запрещающего переупорядочения команд)

❑ Вставьте барьер StoreStore перед каждой изменчивой операцией записи.
❑ Вставьте барьер StoreLoad после каждой операции энергозависимой записи.
❑ Вставляйте барьер LoadLoad после каждой операции нестабильного чтения.
❑ Вставляйте барьер LoadStore после каждой операции энергозависимого чтения.

Вставьте описание изображения здесь
Примеры:
Вставьте описание изображения здесь
Вставьте описание изображения здесь

Опубликовано 37 оригинальных статей · Хвала 6 · Посещений 4637

рекомендация

отblog.csdn.net/littlewhitevg/article/details/105568194