Java volatile и побочные эффекты

Документация Oracle по атомарному доступу (по адресу http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html) говорит следующее:

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

У меня проблемы с обдумыванием этого. Я понимаю, как работают изменчивые переменные (в >= Java 5), ​​но мне интересно, как java решает, какой побочный эффект «привел» к изменению изменчивой переменной.

Итак, я думаю, мой вопрос: какие побочные эффекты даются этой гарантией?

РЕДАКТИРОВАТЬ:

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


person B T    schedule 07.02.2012    source источник
comment
у вас есть неправильное представление о том, как работает кеш, НО энергозависимая запись ОЧИЩАЕТ буферы записи, а не кеш (кэш можно сбрасывать вручную, но, как правило, вы никогда этого не захотите)   -  person bestsss    schedule 07.02.2012
comment
В чем именно заключается мое заблуждение? Похоже, ваш ответ будет отличаться от ответа Тома Хотина - может быть, вы хотите написать альтернативный ответ?   -  person B T    schedule 07.02.2012
comment
нет, это не противоречит его ответу, хотя он (его ответ) не совсем полон. Кэш не сбрасывается (это бы просто уничтожило производительность), а только измененные строки кеша становятся согласованными [проще говоря]. Также это гарантирует отсутствие спекулятивного чтения после записи (это очень важно). Таким образом, когда ЦП возобновляет работу, все остальные будут/могут знать об этой записи (и обо всех предыдущих операциях записи). Вообще говоря, все процессоры делают запись доступной/видимой в некоторый момент, но с энергозависимой записью это просто сейчас. Я не думаю, что SO - хорошая область для объяснения того, как работает когерентность кеша.   -  person bestsss    schedule 07.02.2012
comment
Кстати, под сбросом вы подразумеваете выселение? Очистка кеша обычно означает удаление его записей, а не просто распространение записи.   -  person bestsss    schedule 07.02.2012
comment
Мне было непонятно, что я имел в виду. Просто игнорируйте это заявление. Вопрос: что вы подразумеваете под прошлым письмом?   -  person B T    schedule 07.02.2012
comment
@BT Боюсь, объяснение когерентности кеша в комментариях SO не сработает (но я попробую). По сути, это означает, что ЦП спекулятивно загружает данные из памяти раньше, чем это должно быть на самом деле. Скажем, у нас есть две переменные a и b b, которые являются изменчивыми. Наш код выполняет if (b) read a. В ассемблере: Вместо ld r0, b; jnz r0, ...; ld r0, a процессор мог бы сделать ld r1, a; ld r0, b; jnz r0, ...; И теперь у нас есть очевидное состояние гонки: мы можем прочитать старое значение a и новое значение b и никогда не проверять, изменилось ли a.   -  person Voo    schedule 07.02.2012


Ответы (2)


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

Любые записи в потоке, который записал в volatile до того, как это сделать, будут видны потоку, читающему volatile после того, как он это сделал.

person Tom Hawtin - tackline    schedule 07.02.2012
comment
Ах, это имеет большой смысл. Значит, все содержимое кеша для этого конкретного потока будет сброшено? В Java нет способа указать, что переменная определенно не будет доступна другому потоку (и поэтому ее не нужно сбрасывать)? Или, возможно, компилятор/интерпретатор выполняет какую-то оптимизацию вокруг этого? - person B T; 07.02.2012
comment
Кроме того, после того, как я это сделал, мне нравится ваша формулировка - поэтому чтение из потока A в энергонезависимые, которые были изменены в потоке B, может быть устаревшим, пока поток A не прочитает volatile, измененный потоком B, правильно? - person B T; 07.02.2012
comment
@BT Я не эксперт по когерентности кеша. Это необходимо, чтобы никакой другой кеш не думал, что у него есть чистая копия строки кеша. Конечно, нет необходимости все обесценивать. Локальные переменные могут оставаться в регистрах. Есть некоторая возможность сохранить объекты, выделенные в куче, но она довольно незначительна. / Да. - person Tom Hawtin - tackline; 07.02.2012
comment
@BT Это полностью зависит от используемого протокола когерентности кеша. В общем, нам просто нужно распространить запись на все измененные строки кэша (если протокол предлагает эту информацию - не знаю, что такое ARM, но более сложные процессоры для настольных ПК, безусловно, делают), а не весь кеш. - person Voo; 07.02.2012

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

MyClass c=new MyClass();

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

volatile MyClass c=new MyClass();

Согласно правилам 1.5, назначение гарантированно будет последним из этих событий. «Побочными эффектами» являются выделение памяти и вызов конструктора.

person Nialscorva    schedule 07.02.2012
comment
Это разумный пример, но он не объясняет, как java решает, какую сторону затрагивать для распространения и когда. Ответ Тома очень кратко объясняет это. - person B T; 07.02.2012