Модель памяти Java, называемая JMM, представляет собой единую гарантию для серии платформ виртуальных машин Java на не связанную конкретную платформу для видимости памяти и может ли она быть переупорядочена в многопоточной среде, предоставленной разработчиками. (Может быть неоднозначно с точки зрения термина и распределения памяти времени выполнения Java, которое относится к областям памяти, таким как куча, область метода, стек резьбы и т. Д.).
Есть много стилей одновременного программирования. В дополнение к CSP (последовательный процесс связи), актер и другие модели, наиболее знакомым должна быть модель общей памяти, основанную на потоках и блоках. В многопоточном программировании необходимо обратить внимание на три типа проблем с параллелизмом:
・ Атомность ・ видимость ・ Переупорядочение
Атомность предполагает, могут ли другие потоки видеть промежуточное состояние или мешать, когда поток выполняет композитную работу. Как правило, это проблема i ++. Два потока выполняют операции ++ на общей памяти кучи одновременно. Реализация операций ++ в JVM, среде выполнения и процессора может быть композитной операцией. Например, с точки зрения инструкций JVM, он состоит в том, чтобы прочитать значение I из памяти кучи в стек операнда, добавить его и написать в память кучи i. Во время этих операций, если нет правильной синхронизации, другие потоки также могут выполнять ее одновременно, что может привести к потере данных и другим проблемам. Общие проблемы с атомностью, также известная как конкурентное состояние, оцениваются на основе возможного результата неудачи, таких как чтения-модификационный варит. Проблемы видимости и переупорядоченности как связаны с оптимизацией системы.
Поскольку скорость выполнения ЦП и скорость доступа к памяти серьезно несоответствуют, чтобы оптимизировать производительность, основываясь на принципах локализации, таких как местонахождение времени и пространственная местность, ЦП добавил многослойный кэш между памятью. Когда это необходимо для получения данных, процессор сначала перейдет в кэш, чтобы выяснить, существует ли соответствующий кэш. Если он существует, он будет возвращен напрямую. Если его не существует, он будет извлечен в память и сохранен в кэше. Теперь более многоядерные процессоры стали стандартными, каждый процессор имеет свой собственный кэш, который включает в себя проблему согласованности кэша. ЦП имеют консистенционные модели различных сильных и слабых сторон. Самая сильная последовательность - самая высокая безопасность, и она также соответствует нашему режиму последовательного мышления. Тем не менее, с точки зрения производительности, будет много накладных расходов из -за необходимости скоординированной связи между различными процессорами.
Типичная диаграмма структуры кеша процессора выглядит следующим образом
Цикл инструкций процессора обычно представляет собой инструкции по извлечению инструкций, инструкции по анализу для чтения данных, выполнения инструкций и записи данных обратно в регистры или память. При выполнении инструкций в последовательных данных данные чтения и сохранения занимают много времени, поэтому ЦП обычно использует конвейер инструкций для выполнения нескольких инструкций одновременно для повышения общей пропускной способности, как и заводской трубопровод.
Скорость считывания данных и записи данных обратно в память не соответствует тому же порядку, как инструкции по выполнению, поэтому ЦП использует регистры и кэши, как кэши и буферы. При чтении данных из памяти он будет считывать линию кэша (аналогично чтению диска и считывает блок). Модуль, который записывает данные обратно, поместит запрос на хранение в буфер хранилища, когда старые данные не находятся в кэше, и продолжает выполнять следующий этап цикла инструкций. Если он существует в кэше, кэш будет обновлен, а данные в кэше будут промыть в память в соответствии с определенной политикой.
открытый класс MemoryModel {private int count; Частная логическая остановка; public void initCountandStop () {count = 1; остановка = false; } public void doloop () {while (! Stop) {count ++; }} public void printresult () {System.out.println (count); System.out.println (Stop); }}При выполнении приведенного выше кода мы можем подумать, что count = 1 будет выполнен до остановки = false. Это правильно в идеальном состоянии, показанном на приведенной выше диаграмме выполнения процессора, но при рассмотрении вопроса о буферизации регистра и буферизации кэша оно неверно. Например, сама остановка находится в кэше, но подсчет нет, а затем остановка может быть обновлена, а буфер записи графа обновляется на память перед написанием.
Кроме того, процессор и компилятор (обычно ссылаются на JIT для Java) могут изменить заказ на выполнение инструкции. Например, в приведенном выше коде, count = 1 и stop = false не имеют зависимостей, поэтому ЦП и компилятор могут изменить порядок этих двух. По мнению однопоточной программы, результат такой же. Это также является AS-IF-Serial, который должен обеспечить процессор и компилятор (независимо от того, как изменен порядок выполнения, результат выполнения однопользованного остается неизменным). Поскольку большая часть выполнения программы однопользована, такая оптимизация приемлема и приносит отличные улучшения производительности. Однако в случае многопоточного чтения неожиданные результаты могут возникнуть без необходимых операций синхронизации. Например, после того, как поток T1 выполняет метод initCountandStop, поток T2 выполняет Printresult, который может быть 0, False, 1, false или 0, true. Если поток T1 выполняет doloop () сначала, а поток T2 выполняет initCountandStop на одну секунду, то T1 может выпрыгнуть из цикла, или он может никогда не увидеть модификацию остановки из -за оптимизации компилятора.
Из-за различных задач в вышеупомянутых многопоточных ситуациях последовательность программ в многопользовании больше не является порядком выполнения и приводит к базовому механизму. Язык программирования должен дать разработчикам гарантию. Проще говоря, эта гарантия - когда модификация потока будет видна другим потокам. Таким образом, Java Language предлагает JavamemoryModel, то есть модель памяти Java, которая требует реализации в соответствии с соглашениями этой модели. Java предоставляет такие механизмы, как летучие, синхронизированные и окончательные, чтобы помочь разработчикам обеспечить правильность многопоточных программ на всех платформах процессоров.
До JDK1.5 модель памяти Java была серьезные проблемы. Например, в старой модели памяти поток может увидеть значение по умолчанию окончательного поля после завершения конструктора, а запись летучего поля может быть переупорядочена с помощью чтения и записи нелетущего поля.
Таким образом, в JDK1.5 через JSR133 была предложена новая модель памяти для решения предыдущих задач.
Правила повторного заказа
летучая блокировка и мониторинг
| Возможно ли переупорядочить | Вторая операция | Вторая операция | Вторая операция |
|---|---|---|---|
| Первая операция | Нормальное чтение/обычное письмо | Внедрение изменяемого чтения/монитора | Переписка/монитор |
| Нормальное чтение/обычное письмо | Нет | ||
| Voaltile Read/Monitor Enter | Нет | Нет | Нет |
| Переписка/монитор | Нет | Нет |
Обычное чтение относится к множеству массивов Getfield, GetStatic и нелетучих массивов, а нормальное чтение относится к Arraystore из Putfield, Putstatic и нелетучих массивов.
Читает и написание нестабильных полей - Getfield, GetStatic, Putfield, Putstatic, соответственно.
Monitorenter - это ввести блок синхронизации или метод синхронизации, мониторексист относится к выходу из блока синхронизации или метода синхронизации.
НЕТ В приведенной выше таблице относится к двум операциям, которые не позволяют переупорядочить. Например (нормальное письмо, летучие письма) относится к переупорядочению нелетучих полей и изменению заказа записей любых последующих летучих полей. Когда нет нет, это означает, что переупорядочение разрешено, но JVM необходимо обеспечить минимальную безопасность - значение чтения является либо значением по умолчанию, либо записано другими потоками (64 -разрядные двойные и длинные операции чтения и записи являются особым случаем. Когда нет летучих модификации, не гарантировано, что чтение и запись являются атомными, а базовый слой может разделить его на два отдельных операция).
Финальное поле
Есть два дополнительных специальных правила для окончательного поля
Ни записи о последнем поле (в конструкторе), ни запись о ссылке самого объекта конечного поля не могут быть переупорядочены последующими записями объектов, удерживающих конечное поле (вне конструктора). Например, следующее утверждение не может быть переупорядочено
x.finalfield = v; ...;; sharedref = x;
Первая нагрузка конечного поля не может быть переупорядочена с помощью записи объекта, удерживающего конечное поле. Например, следующее утверждение не позволяет переупорядочить.
x = sharedref; ...;; i = x.finalfield
Барьер памяти
Все процессоры поддерживают определенные барьеры или заборы памяти для контроля видимости переупорядочения и данных между различными процессорами. Например, когда процессор записывает данные обратно, он поместит запрос магазина в буфер записи и будет ждать промывки в память. Этот запрос магазина может быть предотвращена переупорядоченными с помощью других запросов, вставив барьер для обеспечения видимости данных. Вы можете использовать пример срока службы для сравнения барьера. Например, при выборе лифта склона в метро каждый входит в лифт в последовательности, но некоторые люди будут ходить слева, поэтому порядок при выходе из лифта отличается. Если человек несет большой багаж, заблокированный (барьер), люди позади не могут обойти :). Кроме того, барьер здесь и барьера записи, используемые в GC, являются разными понятиями.
Классификация барьеров памяти
Почти все процессоры поддерживают инструкции барьеры определенного грубого зерна, обычно называемого забора (забор, забор), которые могут гарантировать, что инструкции, инициированные нагрузкой и хранилище, инициированные до ограждения, можно строго в порядке с нагрузкой и хранилище после забора. Обычно он будет разделен на следующие четыре типа барьеров в соответствии с их целью.
Загрузки барьеры
Нагрузка1; Загрузка; Нагрузка2;
Убедитесь, что данные загрузки1 загружаются до Load2 и после загрузки
Storestore Barrieres
Магазин1; Storestore; Магазин2
Убедитесь, что данные в Store1 видны другим процессорам до магазина2 и после.
Барьеры для погрузки
Нагрузка1; Loadstore; Магазин2
Убедитесь, что данные Load1 загружены до Store2 и после промывки данных
Барьеры из сторной нагрузки
Магазин1; StoreLoad; Нагрузка2
Убедитесь, что данные в Store1 видны перед другими процессорами (например, промывка в память) перед загрузкой данных в Load2 и после нагрузки. Барьер StoreLoad предотвращает нагрузку от считывания старых данных, а не данных, недавно записанных другими процессорами.
Почти все мультипроцессы в наше время требуют нагрузки. Накладные расходы на нагрузку, как правило, самая большая, и StoreLoad имеет эффект трех других барьеров, поэтому в качестве общего (но более высокого (но более высокого накладного) барьера можно использовать StoreLoad.
Следовательно, используя приведенный выше барьер памяти, могут быть реализованы правила переупорядочения в приведенной выше таблице
| Нужны барьеры | Вторая операция | Вторая операция | Вторая операция | Вторая операция |
|---|---|---|---|---|
| Первая операция | Нормальное чтение | Нормальное письмо | Внедрение изменяемого чтения/монитора | Переписка/монитор |
| Нормальное чтение | Loadstore | |||
| Нормальное чтение | Storestore | |||
| Voaltile Read/Monitor Enter | Загрузка | Loadstore | Загрузка | Loadstore |
| Переписка/монитор | StoreLoad | Storestore |
Чтобы поддержать правила окончательных полей, необходимо добавить барьер к окончательному записи к окончательному
x.finalfield = v; Storestore; sharedref = x;
Вставьте барьер памяти
Основываясь на вышеуказанных правилах, вы можете добавить барьер для обработки летучих полей и синхронизированных ключевых слов для соответствия правилам модели памяти.
Вставьте Storestore перед летучим барьером магазина после написания всех последних полей, но вставьте Storestore до возврата конструктора
Вставьте барьер StoreLoad после нестабильного магазина. Вставьте нагрузку и барьер Loadstore после летучей нагрузки.
Правила монитора ввода и летучей нагрузки являются согласованными, а монитор выход и правила летучих хранилища являются согласованными.
Случиться до
Различные барьеры памяти, упомянутые выше, по -прежнему являются относительно сложными для разработчиков, поэтому JMM может использовать серию правил взаимосвязи частичного порядка, прежде чем проиллюстрировать. Чтобы гарантировать, что поток, который выполняет операцию B, видит результат операции A (независимо от того, выполняются ли A и B в одном и том же потоке), то взаимосвязь между A и B должен быть выполнен между A и B, в противном случае JVM может переупорядочить их произвольно.
Стоимость списка правил
Случайно правила включают
Правила последовательности программ: если операция A в программе находится до операции B, то операция A в том же потоке будет выполнять правила блокировки монитора до работы B: Операция блокировки на блокировке монитора должна быть выполнена перед операцией блокировки на той же блокировке монитора.
Правила летучих переменных: операция записи летучей переменной должна выполнять правила запуска потока, прежде чем операция чтения переменной: вызов по потоку. Начало в потоке должно выполнять правила окончания потока, прежде чем любая операция в потоке: любая операция в потоке должна выполнять правила прерывания, прежде чем другие потоки обнаруживают, что поток завершится: когда поток вызовет операцию, и выполнение выполнения выполнения, прежде чем выполнять прохождение, прежде чем прерывание поток не будет обнаружено, прежде чем выполнять выполнение. Перед тем, как выполнить перерыв, перед тем, как выполнять выполнение. Перед тем, как выполнять, до того, как выполняет перерыв, не будет выполнять выполнение. Перед тем, как выполнять выполнение. Перед тем, как выполнять выполнение, до того, как выполняет выполнение, до того, как выполняет выполнение, перед выполнением. Перед тем, как выполнять выполнение. Перед тем, как выполнить выполнение, до того, как выполняет, до того, как выполняет, прерывание. Операция B выполняется до операции C, затем операция A выполняется до операции C.
Замок дисплея имеет ту же семантику памяти, что и у блокировки монитора, а атомная переменная имеет ту же семантику памяти, что и летучая. Приобретение и выпуск замков, операции чтения и записи летучих переменных удовлетворяют отношению полного порядка, поэтому запись летучего может быть выполнена перед последующими летучими чтениями.
Вышеупомянутый случай, прежде чем можно объединить с использованием нескольких правил.
Например, после потока A входит в блокировку монитора, операция перед выпуском блокировки монитора основана на правилах последовательности программ, а операция выпуска монитора происходит перед тем, как получить ту же блокировку монитора в последующем потоке B и операции в операции.
Суммировать
Выше приведено все подробное объяснение модели памяти Java JMM в этой статье, я надеюсь, что это будет полезно для всех. Если есть какие -либо недостатки, пожалуйста, оставьте сообщение, чтобы указать это. Спасибо, друзья, за вашу поддержку на этом сайте!