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

Происходит до отношений с изменчивыми полями и синхронизированными блоками в Java - и их влияние на энергонезависимые переменные?

Я до сих пор довольно новичок в концепции потоковой обработки и стараюсь больше узнать об этом. Недавно я наткнулся на сообщение в блоге "Что летучие средства в Java" Джереми Мэнсона, где он пишет:

Когда один поток записывает в изменчивую переменную, а другой поток видит которые пишут, первый поток сообщает второй о всемсодержимое памяти до тех пор, пока она не выполнит запись в эту изменчивую переменная. [...] все содержимого памяти, просмотренного Thread 1, до он написал [volatile] ready, должен быть видимым для Thread 2, после него читает значение true для ready. [выделено мной мной]

Теперь, означает ли это, что все переменные (volatile или нет), хранящиеся в памяти Thread 1 во время записи в переменную volatile, станут видимыми для Thread 2 после того, как она прочитает эту изменчивую переменную? Если это так, можно ли скомпрометировать этот оператор вместе с официальными документами Java/Oracle? И из какой версии Java он будет работать?

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

private String s = "running";
private volatile boolean b = false;

И Thread 1 выполняет следующее:

s = "done";
b = true;

Затем Thread 2 затем выполняет (после того, как Thread 1 написал в поле volatile):

boolean flag = b; //read from volatile
System.out.println(s);

Можно ли гарантировать печать "сделано"?

Что произойдет, если вместо объявления b как volatile я помещу запись и чтение в блок synchronized?

Кроме того, в обсуждении, озаглавленном "Связаны ли статические переменные между потоками?, @TREE пишет

Не используйте volatile для защиты более чем одной части общего состояния.

Почему? (Извините, я еще не могу комментировать другие вопросы, или я бы спросил там...)

4b9b3361

Ответ 1

Да, гарантируется, что поток 2 напечатает "done". Конечно, если запись в b в Thread 1 фактически происходит до чтения из b в Thread 2, а не в одно и то же время или раньше!

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

В вашем случае у вас есть следующие события:

  • Тема 1 записывает на s
  • Реферат 1 записывается в b
  • Тема 2 читает b
  • Тема 2 читает из s

И действуют следующие правила:

  • "Если x и y являются действиями одного и того же потока, а x идет до y в порядке программы, тогда hb (x, y)." (правило порядка программы)
  • "Запись в изменчивое поле (§8.3.1.4) происходит до каждого последующего чтения этого поля". (правило нестабильности)

Происходит следующее: до того, как существуют отношения:

  • Запись 1-го потока в s происходит до того, как Thread 1 записывается в b (правило порядка программы)
  • Запись 1-го потока в b происходит до того, как Thread 2 читает из b (volatile rule)
  • Резьба 2 читается из b происходит до того, как Thread 2 читает из s (правило порядка программы)

Если вы следуете этой цепочке, вы можете увидеть, что в результате:

  • Запись 1-го потока в s происходит до того, как Thread 2 читает с s

Ответ 2

Что произойдет, если вместо объявления b как volatile я помещу запись и чтение в синхронизированный блок?

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

Не используйте volatile для защиты более чем одной части общего состояния.

Почему?

volatile не гарантирует атомарность: в вашем примере переменная s также может быть изменена другими потоками после записи, которую вы показываете; поток чтения не будет иметь никаких гарантий относительно того, какое значение он видит. То же самое происходит для записи в s, происходящей после чтения volatile, но до чтения s.

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

Можно ли скомпрометировать этот оператор вместе с официальной документацией Java/Oracle?

Цитаты из спецификации:

17.4.4. Порядок синхронизации

Запись в изменчивую переменную v (§8.3.1.4) синхронизируется со всеми последующими чтениями v любым потоком (где "последующее" определяется в соответствии с порядком синхронизации).

17.4.5. Бывает - до заказа

Если x и y - действия одного и того же потока, а x - до y в программном порядке, то hb (x, y).

Если действие x синхронизируется со следующим действием y, то мы также имеем hb (x, y).

Этого должно быть достаточно.

И из какой версии Java он будет работать?

Спецификация языка Java, 3-е издание ввела переписку спецификации модели памяти, которая является ключом к вышеуказанным гарантиям. NB большинство предыдущих версий действовали так, как если бы гарантии были там, и многие строки кода на самом деле зависели от него. Люди были удивлены, когда узнали, что гарантий на самом деле там не было.

Ответ 3

Можно ли гарантировать печать "сделано"?

Как сказано в Java Concurrency на практике:

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

Значит ДА, это гарантирует печать "сделано".

Что произойдет, если вместо объявления b как изменчивого я ставлю записывать и читать в синхронизированный блок?

Это тоже гарантирует то же самое.

Не используйте volatile для защиты более чем одной части общего состояния.

Почему?

Потому что волатильность гарантирует только видимость. Это не гарантирует атомарность. Если у нас есть две летучие записи в методе, к которому обращается поток A, а другой поток B обращается к этим изменчивым переменным, тогда в то время как thread A выполняет этот метод, возможно, что поток A будет вытесняться потоком B в середине операций (например, после первой volatile write, но до второй volatile write потоком A). Поэтому, чтобы гарантировать, что атомарность операции synchronization является наиболее приемлемым выходом.