Изменчивая переменная в Java - PullRequest
20 голосов
/ 07 июня 2011

Итак, я читаю эту книгу под названием Параллелизм Java на практике , и я застрял в этом единственном объяснении, которое не могу понять без примера. Это цитата:

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

Может ли кто-нибудь дать мне контрпример, почему «значения ВСЕХ переменных, которые были видимы для A до записи в переменную volatile, становятся видимыми для B ПОСЛЕ чтения летучей переменной»?

Я смущен, почему все другие энергонезависимые переменные не становятся видимыми для B до чтения энергозависимой переменной?

Ответы [ 5 ]

25 голосов
/ 07 июня 2011

Объявление изменяемой переменной Java означает:

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

Просто для справки. Когда требуется летучая среда?

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

С JLS §17.4.7 Правильно оформленные казни

Мы рассматриваем только хорошоказни.Выполнение E =

корректно формируется, если выполняются следующие условия:

  1. Каждое чтение видит запись вта же переменная в исполнении.Все чтения и записи изменчивых переменных являются изменчивыми действиями.Для всех чтений r в A мы имеем W (r) в A и W (r) .v = rv Переменная rv является изменчивой тогда и только тогда, когда r является изменяемым чтением, а переменная wv является изменчивой тогда и только тогда, когда wэто изменчивая запись.

  2. Бывает до того, как заказ будет частичным.Порядок «до появления» задается транзитивным замыканием синхронизаций с ребрами и порядком программы.Это должен быть действительный частичный порядок: рефлексивный, транзитивный и антисимметричный.

  3. Выполнение подчиняется внутрипотоковой согласованности.Для каждого потока t действия, выполняемые t в A, являются такими же, как те, которые будут генерироваться этим потоком в программном порядке изолированно, при каждой записи пишется значение V (w), учитывая, что каждое чтение r видит значение V (W (г)).Значения, видимые при каждом чтении, определяются моделью памяти.Указанный программный порядок должен отражать программный порядок, в котором действия будут выполняться в соответствии с семантикой внутри потока P.

  4. Выполнение выполняется до согласования (§17.4.6).

  5. Выполнение подчиняется последовательности порядка синхронизации.Для всех энергозависимых чтений r в A это не тот случай, когда либо так (r, W (r)), либо что существует победа записи A, такая что wv = rv и т. Д. (W (r), w) и т. Д.w, r).

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

15 голосов
/ 07 июня 2011

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

Например, прочитайте следующую ссылку, которая заканчивается «Фиксация блокировки с двойной проверкой с использованием Volatile»:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

6 голосов
/ 07 июня 2011

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

Если переменная теперь объявлена ​​как volatile, то компилятор больше не пытается оптимизировать доступ (чтение и запись) к этой переменной. Однако может продолжать оптимизировать доступ для других переменных.

Во время выполнения при обращении к энергозависимой переменной JVM генерирует соответствующие инструкции барьера памяти для CPU. Барьер памяти служит той же цели - ЦПУ также не позволяет переупорядочивать инструкции.

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

Я укажу на эту запись в Барьеры памяти и параллелизм JVM , если вы хотите понять, как JVM выдает инструкции по барьеру памяти, более подробно.

Смежные вопросы

  1. Что такое забор памяти?
  2. Какие уловки делает процессор для оптимизации кода?
3 голосов
/ 07 июня 2011

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

1 голос
/ 07 июня 2011

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

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

Если вы использовали переменную volatile, как только поток B считывает из него какое-то значение X, виртуальная машина убеждается, что все, что поток A написал перед тем, как записать X, также будет видно B. (И также все, что A гарантировано как видимое, транзитивно).

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

...