Модель памяти Java - кто-то может это объяснить? - PullRequest
28 голосов
/ 12 декабря 2008

В течение многих лет я пытался понять часть спецификации Java, которая касается модели памяти и параллелизма. Я должен признать, что я с треском провалился. Да, я понимаю о блокировках и "синхронизированных" и wait () и notify (). И я могу использовать их просто отлично, спасибо. У меня даже есть смутное представление о том, что делает «изменчивый». Но все это происходило не из языковой спецификации, а из общего опыта.

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

  • Что именно делает "volatile"?
  • Является ли запись в переменную атомарной? Зависит ли это от типа переменной?

Ответы [ 8 ]

33 голосов
/ 12 декабря 2008

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

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


Я предложу немного понимания только по одному аспекту, потому что его часто неправильно понимают:

Есть разница между волатильностью и атомарностью. Люди часто думают, что атомарная запись является энергозависимой (т. Е. Вам не нужно беспокоиться о модели памяти, если запись атомарна). Это не правда.

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

Атомность - это вероятность того, что если изменение будет замечено , будет видна только часть изменения.

Например, взять запись в целочисленное поле. Это гарантированно будет атомным, но не летучим. Это означает, что если у нас есть (начиная с foo.x = 0):

Thread 1: foo.x = 257;
Thread 2: int y = foo.x;

Возможно, что y будет 0 или 257. Это не будет никаким другим значением (например, 256 или 1) из-за ограничения атомарности. Однако, даже если вы знаете, что во «настенное время» код в потоке 2, выполняемый после кода в потоке 1, может произойти странное кэширование, доступ к памяти «движется» и т. Д. Если сделать переменную x volatile, то это исправится.

Остальное оставлю на усмотрение настоящих честных экспертов.

13 голосов
/ 12 декабря 2008
  • non- volatile переменные могут кэшироваться локально, поэтому разные потоки могут одновременно видеть разные значения; volatile предотвращает это ( источник )
  • записи в переменные 32 бит или меньше гарантированно будут атомарными ( подразумевается здесь ); не так для long и double, хотя 64-битные JVM, вероятно, реализуют их как атомарные операции
7 голосов
/ 12 декабря 2008

Я не буду пытаться объяснить эти проблемы здесь, но вместо этого направлю вас к превосходной книге Брайана Гетца на эту тему.

Книга называется «Параллелизм Java на практике», ее можно найти по адресу Amazon или в любом другом магазине компьютерной литературы.

4 голосов
/ 16 декабря 2008

Я недавно нашел отличную статью , которая объясняет изменчивость как:

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

  • Каждый поток в Java происходит в отдельном пространстве памяти (это явно не соответствует действительности, так что потерпите меня на этом).

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

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

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

Дополнительные ссылки: http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html http://www.javaperformancetuning.com/news/qotm030.shtml

4 голосов
/ 12 декабря 2008

Это хорошая ссылка, которая может дать вам немного подробной информации:

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

1 голос
/ 07 апреля 2010

Другие ответы, приведенные выше, абсолютно верны в том смысле, что ваш вопрос не для чутья сердца.

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

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

0 голосов
/ 19 ноября 2013

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

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

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

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

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

Все, что поток написал до выхода из Runnable, видно потоку, который выполняет join (). Все, что поток написал перед выполнением start (), будет видно порожденному потоку.

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

Ничто из этого не является новым, и другие ответы изложили это лучше. Я просто хотел написать это, чтобы очистить голову.

0 голосов
/ 12 декабря 2008

Может пригодиться одно понятие: данные (данные) и копии.

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

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

Так что вы всегда должны помнить об этом. Это верно не только для полей класса Java, но и для переменных cpp, записей базы данных (данные о состоянии записи копируются в несколько сеансов и т. Д.). Переменные, их скрытые / видимые копии и тонкие проблемы синхронизации будут существовать вечно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...