Подтвердить что ты не робот

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

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

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

  • Что делает "volatile", точно?
  • Является ли запись переменной атома? Это зависит от типа переменной?
4b9b3361

Ответ 1

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

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


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

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

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

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

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

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

Возможно, для y должно быть 0 или 257. Это не будет никакое другое значение (например, 256 или 1) из-за ограничения атомарности. Тем не менее, даже если вы знаете, что в "wall time" код в потоке 2 выполняется после кода в потоке 1, может быть нечетное кэширование, доступ к памяти "перемещается" и т.д. Создание переменной x volatile будет исправлять это.

Я оставлю остальных до истинных экспертов честности.

Ответ 2

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

Ответ 3

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

Книга "Java Concurrency in Practice" находится в Amazon или в любом другом сортированном магазине для компьютерной литературы.

Ответ 5

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

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

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

Может быть полезно одно представление: данные (данные) и копии.

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

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

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

Ответ 8

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

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

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

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

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

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

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

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