Модель памяти Java - кто-то может это объяснить?

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

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

  • Что именно делает "volatile"?
  • Является ли запись в переменную atomic? Зависит ли это от типа переменной?
12.12.2008 13:35:13
8 ОТВЕТОВ
РЕШЕНИЕ

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

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


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

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

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

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

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

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

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

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

34
12.12.2008 13:52:06

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

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

7
12.12.2008 13:41:14
  • не volatileпеременные могут кэшироваться локально, поэтому разные потоки могут одновременно видеть разные значения; volatileпредотвращает это ( источник )
  • запись в переменные 32 бит или меньше гарантированно будет атомарной ( подразумевается здесь ); не так для longи double, хотя 64-битные JVM, вероятно, реализуют их как атомарные операции
13
12.12.2008 14:04:35

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

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

4
12.12.2008 14:19:01
Спасибо за ссылку! Он не заменяет книгу (которую я получу), но дал мне одно понимание: я привык думать о синхронизации, а модель памяти больше связана с переупорядочением. Мне нужно научиться различать два и думать о последнем.
user3458 12.12.2008 18:40:04

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

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

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

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

0
12.12.2008 15:04:51

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

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

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

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

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

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

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

4
5.04.2015 19:53:19
Это не N, это N + 1! Смотрите мой собственный ответ :)
user3458 17.08.2012 12:30:07

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

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

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

1
7.04.2010 16:23:17

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

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

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

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

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

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

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

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

0
19.11.2013 15:48:28