Понимание модели памяти Java

Java задняя часть JVM переводчик
Понимание модели памяти Java
文章首发于51CTO技术栈公众号
作者 陈彩华
文章转载交流请联系 caison@aliyun.com

Недавно я повторно изучил «Углубленное изучение виртуальной машины Java» и реорганизовал расплывчатые знания в предыдущей модели памяти Java.Эта статья в основном знакомит с предысторией проблем, порожденных моделью, решенными проблемами, идеи обработки, соответствующие правила реализации и цикл Я надеюсь, что после прочтения этой статьи читатели получат относительно ясное представление о системе модели памяти Java и поймут, почему.

1 Фон генерации модели памяти

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

Проблемы параллелизма с физическими машинами

  • Проблемы с эффективностью оборудования

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

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

CPU高速缓存

  • проблемы с когерентностью кэша

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

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

缓存一致性

  • Проблема оптимизации внеочередного выполнения

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

代码执行乱序优化

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

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

代码乱序执行优化的问题

Приведенный выше рисунок используется в качестве примера для иллюстрации: логика B в ядре2 ЦП зависит от логики А в ядре1, которая будет выполняться первой.

  • Обычно логика B выполняется после выполнения логики A.
  • В случае оптимизации выполнения процессора не по порядку это может привести к тому, что флаг будет установлен в значение true заранее, в результате чего логика B будет выполняться раньше, чем логика A.

2 Композиционный анализ модели памяти Java

Концепции модели памяти

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

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

В частности, модель памяти Java предлагает цель:Определите правила доступа для каждой переменной в программе, то есть базовые детали хранения и извлечения переменных из памяти в виртуальной машине.. Переменная (Variables) здесь отличается от переменной в программировании на Java.Она включает в себя поля экземпляра, статические поля и элементы, составляющие объекты-значения, но не включает локальные переменные и параметры метода, поскольку последние являются потоко-приватными. (Если локальная переменная является ссылочным типом, объект, на который она ссылается, может совместно использоваться различными потоками в куче Java, но сама ссылка находится в таблице локальных переменных стека Java, которая является приватной для потока).

Компоненты модели памяти Java

  • основная память Модель памяти Java предусматривает, что все переменные хранятся в основной памяти (Main Memory).

  • рабочая память Каждый поток имеет свою собственную рабочую память (Рабочая память, также известная как локальная память, которая может быть аналогом описанного ранее кеша процессора), а общие переменные в основной памяти переменных, используемых потоком, хранятся в рабочей памяти. нитки.копия копии.Рабочая память — это абстрактное понятие JMM, которого на самом деле не существует.. Он охватывает кеши, буферы записи, регистры и другие оптимизации оборудования и компилятора.

Абстрактная схематическая диаграмма модели памяти Java выглядит следующим образом:

Java内存模型抽象示意图

Проблемы параллелизма с операциями памяти JVM

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

  • 1 Непротиворечивость данных в рабочей памятиКогда каждый поток обрабатывает данные, он сохраняет копию общей переменной в используемой основной памяти. Когда рабочие задачи нескольких потоков включают одну и ту же общую переменную, соответствующие копии общих переменных будут несогласованными. Если это произойдет, чьи данные копии подлежит ли синхронизация данных обратно в оперативную память? Модель памяти Java в основном обеспечивает согласованность данных с помощью ряда протоколов и правил синхронизации данных, которые будут подробно описаны позже.

  • 2 Оптимизация переупорядочения инструкцийПереупорядочивание в Java обычно представляет собой метод, используемый компилятором или средой выполнения для переупорядочения и выполнения инструкций с целью оптимизации производительности программы. Переупорядочивание делится на две категории:Переупорядочивание во время компиляции и переупорядочение во время выполнения, соответствующие средам времени компиляции и времени выполнения соответственно. Точно так же переупорядочивание инструкций не является случайным переупорядочиванием, оно должно соответствовать следующим двум условиям:

    • 1 Результат выполнения программы не может быть изменен в однопоточной среде Своевременные компиляторы (и процессоры) должны гарантировать, что программы могут учитывать атрибут «как если бы — серийный». С точки зрения непрофессионала, в случае одного потока необходимо дать программе иллюзию последовательного выполнения. То есть результат переупорядоченного выполнения должен соответствовать результату последовательного выполнения.
    • 2 Изменение порядка не допускается, если есть зависимости данных

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

3 Взаимодействие между памятью Java

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

Процесс взаимодействия

Чтобы лучше понять взаимодействие памяти, возьмем в качестве примера взаимодействие потоков, посмотрим, как синхронизировать значения между потоками:

线程间交互操作

И у потока 1, и у потока 2 в основной памяти есть копии общей переменной x. Изначально значение x в этих трех памяти равно 0. После обновления значения x в потоке 1 на 1 синхронизация с потоком 2 в основном состоит из двух шагов:

  • 1 Поток 1 обновляет обновленное значение x из рабочей памяти потока в основную память.
  • 2 Поток 2 считывает переменную x, которую поток 1 обновил ранее в основной памяти.

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

Основные операции взаимодействия с памятью

Что касается конкретного протокола взаимодействия между основной и рабочей памятью, то есть того, как переменная копируется из основной памяти в рабочую память, как синхронизироваться из рабочей памяти обратно в основную память и других деталей реализации, Java Модель памяти определяет следующие 8 операций.

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

8 основных операций

8种基本操作

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

4 правила работы модели памяти Java

4.1 Три характеристики основных операций взаимодействия с памятью

Перед введением конкретных 8 базовых операций взаимодействия памяти необходимо ввести три особенности операции, а модель памяти Java установлена ​​вокруг того, как обрабатывать эти три функции в процессе параллелизма, здесь определяется здесь. И Краткое внедрение базовой реализации, постепенно исследует анализ.

  • атомарность То есть операция или несколько операций либо выполняются все и процесс выполнения не прерывается никакими факторами, либо ни одна из них не выполняется.. Даже когда несколько потоков выполняются вместе, после запуска операции она не будет прервана другими потоками.

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

  • ЗаказПравила упорядочения проявляются в следующих двух сценариях: внутрипотоковом и межпоточном.

    • внутри потока С точки зрения потока выполнение метода выполняется способом, называемым «последовательным» (as-if-serial), который использовался в языках последовательного программирования.
    • между нитями Когда этот поток «наблюдает» за другими потоками, одновременно выполняющими несинхронизированный код, любой код потенциально может чередоваться из-за оптимизации переупорядочения инструкций. Единственные действующие ограничения: для синхронизированных методов синхронизированные блоки (измененные с помощью ключевого слова synchronized) и операции с изменчивыми полями остаются относительно упорядоченными.

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

4.2 происходит до отношений

Прежде чем представить серию правил, давайте сначала разберемся с отношением «происходит до»: используется для описания видимости памяти следующих двух операций:Если операция А происходит раньше операции Б, то результат А виден Б. Анализ отношений «происходит до» должен быть разделен наоднопоточная и многопоточнаяСлучай:

  • однопоточный происходит доПоследовательность байт-кодов естественным образом включает отношение «происходит до»: поскольку один поток совместно использует рабочую память, проблем с согласованностью данных не возникает. Более ранний байт-код в пути потока управления программой происходит до более позднего байт-кода, то есть после выполнения более раннего байт-кода, результат операции виден более позднему байт-коду. Однако это не означает, что первое обязательно выполняется раньше второго. На самом деле, если последние не зависят от результатов первых, их можно переупорядочить.

  • бывает-раньше под многопоточностьюМногопоточность Поскольку каждый поток имеет копию общей переменной, если общая переменная не синхронизирована, после того как поток 1 обновит значение общей переменной операции A, поток 2 начнет выполнять операцию B. В это время результат операции А влияет на операцию Б. Не обязательно видно.

Для облегчения разработки программ в модели памяти Java реализованы следующие операции, поддерживающие отношение «происходит до»:

  • правила порядка программы Внутри потока операции, написанные ранее, выполняются раньше, чем операции, написанные позже, в порядке кода.
  • правило блокировки Операция разблокировки происходит перед последующей операцией блокировки той же блокировки.
  • правила изменяемой переменной Запись в переменную происходит до чтения этой переменной.
  • правило доставки Если операция A происходит раньше операции B, а операция B происходит раньше операции C, то из этого следует, что операция A происходит раньше операции C.
  • правило начала потока Метод start() объекта Thread выполняется перед каждым действием этого потока.
  • Правила прерывания потока Вызов метода прерывания() потока происходит до того, как код прерванного потока обнаружит возникновение события прерывания.
  • правила завершения потока Все операции в потоке происходят — до обнаружения завершения потока мы можем определить, что поток прекратил выполнение с помощью конца метода Thread.join() и возвращаемого значения Thread.isAlive().
  • Правила завершения объекта Инициализация объекта завершает начало метода Happens-Before His Finalize()

4.3 Барьеры памяти

Как обеспечить порядок и видимость базовых операций в Java? Может преодолевать барьеры памяти.

Барьер памяти — это инструкция, вставленная между двумя инструкциями ЦП, чтобы предотвратить переупорядочивание инструкций процессора (например, барьер), тем самым гарантируяупорядоченностьиз. Кроме того, для достижения эффекта барьера он также заставит процессор записывать и считывать значение, записывать значение основной памяти в кеш и очищать недопустимую очередь, чтобы обеспечитьвидимость.

Например:

Store1; 
Store2;   
Load1;   
StoreLoad;  //内存屏障
Store3;   
Load2;   
Load3;

Для приведенного выше набора инструкций ЦП (Store представляет собой инструкцию записи, а Load представляет инструкцию чтения) инструкция Store перед барьером StoreLoad не может поменяться позициями с инструкцией Load после барьера StoreLoad, то естьизменение порядка. Но инструкции до и после барьера StoreLoad взаимозаменяемы, то есть Store1 можно поменять местами с Store2, а Load2 можно поменять местами с Load3.

Есть 4 общих барьера

  • Нагрузочный барьер: Для такого оператора Загрузить1; ЗагрузитьЗагрузить; Загрузить2 до того, как данные, которые должны быть прочитаны Загрузкой2, и последующие операции чтения будут доступны, гарантируется, что данные, которые должны быть прочитаны Загрузкой1, были прочитаны.
  • Барьер магазина: Для такого оператора Store1, StoreStore, Store2 операции записи Store1 гарантированно будут видны другим процессорам до выполнения Store2 и последующих операций записи.
  • Барьер LoadStore: Для такого оператора Load1; LoadStore; Store2 данные, которые должны быть прочитаны с помощью Load1, гарантированно будут прочитаны до того, как Store2 и последующие операции записи будут выполнены.
  • Барьер StoreLoad: Для инструкции Store1; StoreLoad; Load2 запись Store1 гарантированно будет видна всем процессорам до выполнения Load2 и всех последующих операций чтения. Его накладные расходы — самый большой из четырех барьеров (очистка буфера записи, очистка очереди аннулирования). В большинстве реализаций процессоров этот барьер является универсальным барьером, выполняющим функции трех других барьеров памяти.

Использование барьеров памяти в Java не так просто увидеть в общем коде.Обычно есть блоки кода, модифицированные ключевыми словами volatile и synchronized (которые будут введены позже), и барьеры памяти также можно использовать через класс Unsafe.

4.4 Правила для 8 видов синхронизации операций

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

  • Правило 1: Если вы хотите скопировать переменную из основной памяти в рабочую память, вам нужно выполнить операции чтения и загрузки последовательно.Если вы синхронизируете переменную из рабочей памяти обратно в основную память, вам нужно выполнить магазин и запись в последовательности. Но модель памяти Java требует только последовательного выполнения вышеуказанных операций, и нет никакой гарантии, что они должны выполняться последовательно.
  • Правило 2: Одна из операций чтения и загрузки, сохранения и записи не может появляться одна.
  • Правило 3: потоку не разрешается отбрасывать свою последнюю операцию присваивания, т. е. переменная должна быть синхронизирована с основной памятью после того, как она была изменена в рабочей памяти.
  • Правило 4. Потоку не разрешается синхронизировать данные из рабочей памяти обратно в основную память без причины (не выполнялась операция присваивания).
  • Правило 5: Новую переменную можно создать только в основной памяти, прямое использование неинициализированной (загрузить или присвоить) переменной в рабочей памяти не допускается. То есть перед реализацией операций использования и сохранения для переменной сначала должны быть выполнены операции загрузки или назначения.
  • Правило 6: переменная позволяет только одному потоку одновременно выполнять над ней операции блокировки, но операция блокировки может многократно выполняться одним и тем же потоком. Таким образом, блокировка и разблокировка должны быть сопряжены.
  • Правило 7: Если операция блокировки выполняется над переменной, значение переменной в рабочей памяти будет очищено.Прежде чем исполнительный механизм использует переменную, операцию загрузки или назначения необходимо выполнить повторно, чтобы инициализировать значение переменной. переменная.
  • Правило 8: Если переменная ранее не была заблокирована операцией блокировки, ей не разрешается выполнять операцию разблокировки, а также не разрешается разблокировать переменную, заблокированную другим потоком.
  • Правило 9: Перед выполнением операции разблокировки переменной переменная должна быть синхронизирована с основной памятью (операции сохранения и записи)

Кажется, что эти правила немного громоздки, но понять их несложно:

  • Правило 1, Правило 2 Общая переменная в рабочей памяти используется как копия основной памяти Значение переменной основной памяти синхронизируется с рабочей памятью и должно использоваться вместе с чтением и загрузкой Значение переменной в рабочей памяти синхронизируется обратно в основную память и должна использоваться вместе с сохранением и записью.Каждый из этих двух наборов операций является фиксированным и упорядоченным сочетанием, и ему не разрешается появляться отдельно.
  • Правило 3, Правило 4 Поскольку общие переменные в рабочей памяти являются копиями основной памяти, для обеспечения согласованности данных, когда переменные в рабочей памяти переназначаются механизмом байт-кода, они должны быть синхронизированы обратно в основную память. Если переменные в рабочей памяти не обновляются, синхронизация с основной памятью невозможна без причины.
  • Правило 5 Поскольку разделяемые переменные в рабочей памяти являются копиями основной памяти, они должны рождаться из основной памяти.
  • Правила 6, 7, 8, 9 Чтобы безопасно использовать переменные в параллельных условиях, поток может монополизировать переменную в основной памяти на основе операции блокировки, и другим потокам не разрешается использовать или разблокировать переменную, пока переменная не будет разблокирована потоком.

4.5 Специальные правила для volatile переменных

В китайском языке слово volatile означает «нестабильный» и «изменчивый».

Семантика volatile

volatile в основном имеет следующие две семантики

Семантика 1 Гарантированная видимость

Гарантирует видимость в памяти операций над этой переменной разными потоками.

Гарантия видимости здесь не эквивалентна безопасности параллельных операций с volatile-переменными, и гарантия видимости объясняется подробно:

Процесс записи volatile переменной потоком:

  • 1 Изменить значение копии volatile переменной в рабочей памяти потока
  • 2 Сбросить значение измененной копии из рабочей памяти в основную память

Процесс чтения потоком volatile переменной:

  • 1 Считать последнее значение изменчивой переменной из основной памяти в рабочую память потока
  • 2 Прочитать копию volatile переменной из рабочей памяти

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

Например: Определите volatile int count = 0, 2 потока выполняют операцию count++ одновременно, каждый поток выполняется 500 раз, а конечный результат меньше 1000. Причина в том, что каждому потоку требуются следующие 3 шага для выполнения count++:

  • Шаг 1. Поток считывает последнее значение счетчика из основной памяти.
  • Шаг 2 Механизм выполнения увеличивает значение счетчика на 1 и присваивает его рабочей памяти потока.
  • Шаг 3 Рабочая память потока сохраняет значение счетчика в основной памяти. Возможно, что в определенный момент значения, прочитанные двумя потоками на шаге 1, все равны 100, а значения, полученные после выполнения шага 2, все 101. Наконец, 101 дважды обновляется и сохраняется в основную память .

Семантика 2 запрещает изменение порядка инструкций

Чтобы объяснить более конкретно, правила, запрещающие изменение порядка, следующие:

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

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

Например:

volatile boolean initialized = false;

// 下面代码线程A中执行
// 读取配置信息,当读取完成后将initialized设置为true以通知其他线程配置可用
doSomethingReadConfg();
initialized = true;

// 下面代码线程B中执行
// 等待initialized 为true,代表线程A已经把配置信息初始化完成
while (!initialized) {
     sleep();
}
// 使用线程A初始化好的配置信息
doSomethingWithConfig();

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

Принцип реализации volatile переменной

Конкретный метод реализации заключается в добавлении барьера памяти в последовательность инструкций при генерации байт-кода во время компиляции.Следующая стратегия вставки барьера памяти JMM основана на консервативной стратегии:

volatile型变量内存屏障插入策略

  • Вставьте барьер StoreStore перед каждой операцией энергозависимой записи. В дополнение к тому, что операции записи перед барьером и операции записи после барьера не могут быть переупорядочены, барьер также гарантирует, что перед операцией записи volatile любые операции чтения и записи будут зафиксированы до volatile.

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

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

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

сценарии использования volatile переменных

Подводя итог, можно сказать, что "написать один раз, прочитать везде", один поток отвечает за обновление переменной, другие потоки только читают переменную (не обновляют переменную) и выполняют соответствующую логику в соответствии с новым значением переменной. Например, обновляются флаги состояния и публикуются значения переменных модели наблюдателя.

4.6 Специальные правила для конечных переменных

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

4.7 Специальные правила для синхронизированных

Управляйте чтением и записью данных через область кода, заключенную в ключевое слово synchronized:

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

4.8 Специальные правила для длинных и двойных переменных

Модель памяти Java требует, чтобы восемь операций блокировки, разблокировки, чтения, загрузки, назначения, использования, хранения и записи были атомарными, но для 64-битных типов данных (длинных и двойных) в модели определены относительно свободные правила. : позволяет виртуальной машине разделить операции чтения и записи 64-битных данных, не измененных volatile, на две 32-битные операции. Другими словами, виртуальная машина может не гарантировать атомарность четырех операций загрузки, сохранения, чтения и записи 64-битного типа данных. Из-за этой неатомарности другие потоки могут прочитать значение «32-битной половинной переменной», синхронизация которой не завершена.

Однако в реальной разработке модель памяти Java настоятельно предполагает, что виртуальная машина реализует чтение и запись 64-битных данных как атомарные.В настоящее время коммерческие виртуальные машины на различных платформах предпочитают обрабатывать операции чтения и записи 64-битных данных. data как атомарные операции, поэтому обычно нам не нужно объявлять переменные long и double, используемые как volatile при написании кода.

5 Резюме

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

Процесс изучения знаний эквивалентен не просто пониманию знаний и запоминанию знаний, а установлению связей между входом и выходом проблемы, которую решает знание., Суть знания заключается в решении проблем, поэтому, прежде чем учиться, мы должны понять проблему, понять вывод и вывод этой проблемы, а знание представляет собой сопоставление отношений от входа к выходу. Заучивание знаний должно сочетаться с большим количеством примеров, чтобыПоймите эту взаимосвязь сопоставления, а затем сократите знания, Хуа Luogeng сказал: "Читай книгу толстым, а затем читай тонкий", что объясняет эту истину, сначала объединить большое количество примеров, чтобы понять знания, а затем сжать знания.

Возьмем в качестве примера изучение модели памяти Java:

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

Я надеюсь, что все должны помочь.

Более интересно, добро пожаловать на официальный аккаунт автора [архитектура распределенной системы]

Ссылаться на

«Виртуальная машина Java для глубокого обучения»

Углубленный демонтаж виртуальной машины Java

Базовая технология Java 36 лекций

Синхронизация и модель памяти Java - Дуг Ли

Глубокое понимание модели памяти Java

Барьеры памяти Java и видимость

Принцип барьера памяти и синхронизированный и изменчивый

Alibaba Cloud недавно начала выдавать ваучеры, которые могут получить бесплатно как новые, так и старые пользователи. Новые зарегистрированные пользователи могут получить ваучеры на 1000 юаней, а старые пользователи могут получить ваучеры на 270 юаней.Рекомендуется всем получить копию.В любом случае, это бесплатно.Может быть, вам это понадобится в будущем? Коллекция облачных ваучеров AlibabaPromotion.aliyun.com/ваша ТМ есть/облака боятся жары…

Популярные события Специальные предложения для высокопроизводительных облачных серверов, помогающие предприятиям перейти в облако. Скидка 2–5 % на хосты уровня производительности.Promotion.aliyun.com/ваш ТМ is/act/en…