Модель памяти Java и правила «происходит до»

интервью Java задняя часть
Модель памяти Java и правила «происходит до»

Оригинальная статья, краткое изложение опыта и жизненные перипетии на всем пути от набора в школу до фабрики А

Нажмите, чтобы узнать подробностиwww.codercc.com

1. Введение в JMM

обобщено в предыдущей статьеПереходы состояний потока и некоторые основные операции, я уже немного разбираюсь в многопоточности.Если многопоточное программирование так просто, то нам не нужно прилагать большие усилия, чтобы изучить его. Если вы не будете осторожны в многопоточности, возникнут проблемы безопасности потоков, так что же такое проблемы безопасности потоков? Насколько я понимаю, результат выполнения кода при многопоточности несовместим с ожидаемым правильным результатом, код небезопасен для потоков, в противном случае он безопасен для потоков. Хотя этот ответ, похоже, ничего не дает, вы можете его погуглить. Определение можно увидеть в разделе >. Оригинальный текст выглядит следующим образом: Когда несколько потоков обращаются к одному и тому же объекту, если планирование и альтернативная работа этих потоков в среде выполнения не учитываются, дополнительная синхронизация не требуется или любая другая операция координации выполняется в вызывающем объекте для вызова поведения этого объекта. может получить правильный результат, то объект потокобезопасен.

Понимание определения заключается в том, что доброжелательный видит доброжелательный, а мудрый видит мудрость. Проблема безопасности потоков обычно связана с тем, чтоНесоответствие данных основной и рабочей памятииизменение порядкаВызвано, и самое главное для решения проблемы безопасности потоков — это понять, как возникают эти две проблемы, тогда ядро ​​их понимания лежит в понимании модели памяти Java (JMM).

В многопоточных условиях несколько потоков определенно будут сотрудничать друг с другом для выполнения одной задачи, что обычно включает в себяНесколько потоков взаимодействуют друг с другом, чтобы информировать друг друга о состоянии и текущем результате выполнения.и т. Д. Кроме того, для оптимизации производительности, такжеВключает переупорядочение инструкций компилятора и переупорядочение инструкций процессора.. Ниже мы обсудим эти знания один за другим.

2. Абстрактная структура модели памяти

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

Мать Сяо Мина собирается на работу. Это очень срочно. В это время мобильный телефон не работает, поэтому она положила на стол записку "Рис готов, положите его..." Когда Сяо Мин пришел дома, он увидел, что записка была съедена Еда, приготовленная моей матерью, затем, если мать Сяомина и Сяомин используются как два потока, то эта заметка является общей переменной для связи между двумя потоками и сотрудничества между двумя потоками. потоки реализуются путем чтения и записи общей переменной;

Другой способ заключается в том, что в мобильном телефоне матери все еще есть электричество, и мать позвонила Сяо Мину по дороге, чтобы сесть на автобус.Этот метод является механизмом уведомления для завершения сотрудничества. Точно так же его можно распространить на механизм межпотокового взаимодействия.

Через приведенный выше пример должно быть некоторое понимание. При параллельном программировании необходимо решить две основные проблемы:1. Как взаимодействовать между потоками 2. Как выполнить синхронизацию между потоками(Здесь потоки относятся к одновременно выполняющимся активным объектам). Коммуникация относится к механизму, с помощью которого потоки обмениваются информацией. Существует два основных типа: общая память и передача сообщений. Здесь два приведенных выше примера можно сравнить по отдельности. Модель памяти JavaМодель параллелизма для разделяемой памяти, Неявная связь между потоками в основном осуществляется путем чтения и записи общих переменных. Если программисты не понимают модель разделяемой памяти Java, они неизбежно столкнутся со всевозможными проблемами с видимостью памяти при написании параллельных программ.

1. Что такое общие переменные

в java программе всеПоля экземпляра, статические поля и элементы массиваВсе они размещаются в динамической памяти (доступной для всех потоков и могут совместно использоваться), а локальные переменные, параметры определения метода и параметры обработчика исключений не используются совместно потоками. Общие данные будут иметь проблемы с безопасностью потоков, а не общие данные не будут иметь проблем с безопасностью потоков. Об области оперативной памяти JVM будет рассказано в следующей статье.

2. Модель абстрактной структуры JMM

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

JMM内存模型的抽象结构示意图

На рисунке показана абстрактная схематическая диаграмма JMM.Для завершения связи между потоком A и потоком B требуются следующие два шага:

  1. Поток A считывает общую переменную из основной памяти в рабочую память потока A и обрабатывает ее, а затем перезаписывает данные обратно в основную память;
  2. Поток B считывает последнюю общую переменную из основной памяти.

Глядя горизонтально, кажется, что поток A и поток B неявно взаимодействуют через общие переменные. В этом есть очень интересная проблема: если данные не будут записаны обратно в оперативную память вовремя после обновления потока А, а поток Б в это время прочитает просроченные данные, то будет явление "грязного чтения" . Это можно решить с помощью механизмов синхронизации (управление относительным порядком выполнения операций между различными потоками) или с помощью ключевого слова volatile, чтобы каждую переменную volatile можно было принудительно сбросить в основную память, сделав ее видимой для каждого потока.

3. Изменение порядка

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

从源码到最终执行的指令序列的示意图

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

Как показано на рисунке, 1 относится к переупорядочению компилятора, а 2 и 3 вместе называются переупорядочением процессора. Эти переупорядочивания могут привести к проблемам безопасности потоков.Классическим примером является проблема DCL, которая будет подробно обсуждаться в следующих статьях.Переупорядочивание для компилятора, правила переупорядочивания компилятора JMM запрещают некоторыеПереупорядочивание компилятора для конкретного типа;Переупорядочить процессоры, компилятор передастВставьте инструкции барьера памяти, чтобы предотвратить какое-либо специальное переупорядочение процессора..

Итак, при каких обстоятельствах повторный заказ не может быть выполнен? Поговорим о зависимостях данных. Есть следующий код:

double pi = 3.14 //A

double r = 1.0 //B

double area = pi * r * r //C

Это код для вычисления площади круга.Поскольку между А и В нет связи, то и между конечными результатами не будет связи, а порядок выполнения между ними можно переупорядочить. Следовательно, порядок выполнения может быть A->B->C или B->A->C. Конечный результат равен 3,14, то есть между A и B нет зависимости данных. Конкретное определение:Если две операции обращаются к одной и той же переменной, и одна из двух операций является операцией записи, то эти две операции имеют зависимости данных.Здесь возможны три ситуации: 1. Запись после чтения 2. Запись после записи 3. Чтение после записи, все три операции зависят от данных, и изменение порядка повлияет на окончательный результат выполнения.Компилятор и процессор будут учитывать зависимости данных при изменении порядка, а компилятор и процессор не изменят порядок выполнения двух операций, которые имеют зависимости данных.

К тому же тут более интересная как бы серийная семантика.

as-if-serial

Семантика «как если бы» означает, что результат выполнения (однопоточной) программы не может быть изменен, как бы он ни был переупорядочен (компиляторы и процессоры для обеспечения параллелизма). Компилятор, среда выполнения и процессор должны подчиняться семантике «как если бы — последовательная». семантика as-if-serial защищает однопоточные программы,Компиляторы, среды выполнения и процессоры, которые подчиняются семантике как-будто-последовательной, вместе создают иллюзию для программистов, пишущих однопоточные программы, что однопоточные программы выполняются в порядке выполнения программы.. Например, в приведенном выше коде для вычисления площади круга в одном потоке люди будут чувствовать, что код выполняется построчно.На самом деле между строками A и B нет зависимости данных, и они могут быть переупорядочены, то есть А и Б не выполняются последовательно. Семантика «как если бы» освобождает программистов от беспокойства о проблемах переупорядочивания в одном потоке, мешающем им, или о проблемах с видимостью памяти.

4. Бывает - до правила

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

4.1 происходит до определения

Понятие «происходит-прежде» первоначально было предложено Лесли Лэмпортом в одной из его далеко идущих статей («Время, часы и порядок событий в распределенной системе»), вы можете погуглить, если вам интересно. JSR-133 использует концепцию «происходит до» для указания порядка выполнения между двумя операциями. Поскольку эти две операции могут выполняться внутри потока, они также могут выполняться между разными потоками. следовательно,JMM может предоставить программистам гарантии видимости памяти между потоками посредством отношений «происходит до».(Если между операцией записи a потока A и операцией чтения b потока B существует взаимосвязь между операциями записи a и b потока B, хотя операция a и операция b выполняются в разных потоках, JMM уверяет программиста, что операция a будет выполнена. быть видимым для операции b). Конкретное определение:

1) Если действие происходит-перед другим действием, результат выполнения первого действия будет виден второму действию, а порядок выполнения первого действия предшествует второму действию.

2) Наличие отношения «происходит до» между двумя операциями не означает, что конкретная реализация платформы Java должна выполняться в порядке, указанном отношением «произошло до». Если результат выполнения после переупорядочения согласуется с результатом выполнения в соответствии с отношением «происходит до», то такой вид переупорядочения не является незаконным (то есть JMM разрешает такой вид переупорядочения).

выше1) это обязательство JMM перед программистами. С точки зрения программиста отношение «происходит до» можно понимать следующим образом: если А происходит до В, то модель памяти Java гарантирует программисту, что результат операции А будет виден В, и что выполнение А порядок находится перед B. Обратите внимание, что это всего лишь гарантия, которую дает программисту модель памяти Java!

выше2) Это принцип ограничений JMM на переупорядочивание компиляторов и процессоров.. Как упоминалось ранее, JMM на самом деле следует основному принципу: пока он не меняет результат выполнения программы (имеется в виду однопоточные программы и должным образом синхронизированные многопоточные программы), компилятор и процессор могут его оптимизировать. Причина, по которой JMM делает это, заключается в том, что программистов не волнует, действительно ли переупорядочены эти две операции, а то, что семантика выполнения программы не может быть изменена (то есть результат выполнения не может быть изменен). Таким образом, отношение «происходит до» по существу такое же, как и семантика «как если бы».

Давайте сравним «как-будто-сериал» и «происходящее-раньше»:

as-if-serial VS happens-before

  1. Семантика «как если бы» гарантирует, что результат выполнения однопоточной программы не изменится, а отношение «происходит до» гарантирует, что результат выполнения правильно синхронизированной многопоточной программы не изменится.
  2. семантика как-будто-последовательная создает иллюзию для программистов, пишущих однопоточные программы: однопоточные программы выполняются в порядке выполнения программы. Отношение «происходит до» создает иллюзию для программистов, которые пишут правильно синхронизированные многопоточные программы: правильно синхронизированные многопоточные программы выполняются в порядке, указанном в «происходит до».
  3. Цель семантики «как если бы» и «происходит раньше» состоит в том, чтобы максимально улучшить параллелизм выполнения программы без изменения результата выполнения программы.

4.2 Особые правила

Есть восемь конкретных правил:

  1. Правила порядка выполнения программы: каждая операция в потоке выполняется до любых последующих операций в этом потоке.
  2. Следите за правилами блокировки: отпирание замка происходит до последующего запирания замка.
  3. Правило изменчивой переменной: запись в изменчивое поле происходит до любых последующих чтений изменчивого поля.
  4. Переходный: если А происходит раньше В, а В происходит раньше С, то А происходит раньше С.
  5. Правило start(): если поток A выполняет операцию ThreadB.start() (запуская поток B), то операция ThreadB.start() потока A происходит до любой операции в потоке B.
  6. Правило join(): если поток A выполняет операцию ThreadB.join() и завершается успешно, то любая операция в потоке B происходит до того, как поток A успешно завершится из операции ThreadB.join().
  7. Правила прерывания программы: вызов метода thread interrupted() предшествует коду прерванного потока, чтобы определить время прерывания.
  8. Правило завершения объекта: инициализация объекта завершается (конец выполнения конструктора) до начала его метода finalize().

Ниже приведенКонкретный пример того, как использовать эти правила, чтобы делать выводы:

Описание расчета площади круга еще описано выше. Существует три отношения «происходит до» с использованием правила порядка программы (правило 1): 1. А происходит до В; 2. В происходит до С; 3. А происходит до С. Третье отношение здесь выводится с использованием транзитивности. A происходит до B, определение 1 требует, чтобы результат выполнения A был виден B, а порядок выполнения операции A был до операции B, но в то же время, используя второй пункт в определении, операции A и B не имеют зависимостей данных друг от друга, порядок выполнения двух операций не повлияет на конечный результат.Исходя из того, что конечный результат не изменяется, две операции A и B могут быть переупорядочены, то есть происходит Отношение -before не представляет окончательный порядок выполнения.

5. Резюме

Выше обсуждались два аспекта JMM: 1. Абстрактная структура JMM (основная память и рабочая память потоков) 2. Правила переупорядочивания и «происходит до». Далее подведем итоги. Рассмотрим с двух сторон. 1. Какие аспекты следует учитывать при проектировании JMM, то есть какие функции берет на себя JMM 2. Взаимосвязь между «происходит до» и JMM 3. Из-за JMM какие проблемы могут возникнуть в случае многопоточности?

5.1 Дизайн JMM

JMM层级图

JMM — это модель памяти на уровне языка, в моем понимании, JMM находится на среднем уровне и включает в себя два аспекта: (1) модель памяти; (2) правила переупорядочивания и «происходит до». В то же время последовательности инструкций компилятора и процессора контролируются, чтобы запретить определенные типы переупорядочивания. Верхний уровень будет иметь ключевые слова, основанные на JMM, и некоторые специальные классы в пакете J.U.C, чтобы облегчить программистам быстрое и эффективное выполнение параллельного программирования. С точки зрения разработчика JMM, при разработке JMM необходимо учитывать два ключевых фактора:

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

Еще очень интересна проблема переупорядочивания, проще говоря, переупорядочивание можно разделить на две категории:

  1. Изменится переупорядочение результатов выполнения программы.
  2. Не изменяет порядок результатов выполнения программы.

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

  1. Для переупорядочения, которое изменяет результат выполнения программы, JMM требует, чтобы компилятор и процессор запрещали такое переупорядочение.
  2. Для переупорядочивания, не меняющего результат выполнения программы, JMM не требует компилятора и процессора (JMM это позволяет изменение порядка)

Чертеж конструкции JMM:

JMM设计示意图

Из рисунка видно, что:

  1. Правила «происходит до», предоставляемые JMM программистам, могут удовлетворить потребности программистов. Правила JMM «происходит до» не только просты и легки для понимания, но также предоставляют программистам достаточно надежные гарантии видимости памяти (некоторые гарантии видимости памяти могут фактически не существовать, например, вышеприведенное А происходит до Б).
  2. JMM имеет как можно меньше ограничений на компилятор и процессор. Из вышеприведенного анализа видно, что JMM фактически следует основному принципу: до тех пор, пока результат выполнения программы (имеется в виду однопоточные программы и правильно синхронизированные многопоточные программы) не изменяется, компилятор и процессор можно оптимизировать. Например, если компилятор после тщательного анализа определяет, что доступ к блокировке будет осуществляться только одним потоком, блокировку можно снять. Другой пример: если компилятор определяет, что доступ к изменчивой переменной может получить только один поток после тщательного анализа, то компилятор может рассматривать эту изменчивую переменную как обычную переменную. Эти оптимизации не изменят результат выполнения программы, но также повысят эффективность выполнения программы.

5.2 Взаимосвязь между «происходит до» и JMM

happens-before与JMM的关系

Правило «происходит до» соответствует одному или нескольким правилам переупорядочения компилятора и процессора. Для Java-программистов правило «происходит до» простое и легкое для понимания, что избавляет их от необходимости изучать сложные правила переупорядочивания и конкретные методы реализации этих правил, чтобы понять гарантию видимости памяти, предоставляемую JMM.

5.3 Вопросы, которые могут потребовать внимания в будущем

Из приведенной выше абстрактной структуры памяти это может быть связано с явлением «грязного чтения» данных, котороевидимость данныхКроме того, если в многопоточности не обращать внимания на переупорядочивание, легко могут возникнуть некоторые проблемы, например, очень классическая проблема — DCL (блокировка с двойной проверкой), которая требуетсяОтключить переупорядочениеКроме того, при многопоточности атомарные операции, такие как i++, также подвержены проблемам с безопасностью потоков, если на них не обращают внимания. Но в целом при разработке с несколькими потоками нужно начинать сАтомарность, Порядок, ВидимостьРассмотрены три аспекта. Классы параллельных инструментов и параллельные контейнеры в пакете J.U.C также требуют времени для освоения, и эти вещи будут обсуждаться один за другим в следующих статьях.

использованная литература

Искусство параллельного программирования на Java