2009-08-29 5 views
11

Предположим, что у меня есть следующий кодЛетучие семантический относительно других полей

private volatile Service service; 

public void setService(Service service) { 
    this.service = service; 
} 

public void doWork() { 
    service.doWork(); 
} 

Modified поле помечено как изменчивы и его значение не зависит от предыдущего состояния. Итак, это правильный многопоточный код (не беспокойтесь о Service реализациях за минуту).

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

Означает ли это, что следующий код верен?

private volatile boolean serviceReady = false; 
private Service service; 

public void setService(Service service) { 
    this.service = service; 
    this.serviceReady = true; 
} 

public void doWork() { 
    if (serviceReady) { 
    service.doWork(); 
    } 
} 

ответ

17

Да, этот код является «правильным» в его нынешнем виде, начиная с Java 1.5.

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

Любая запись в изменчивую переменную устанавливает связь «произошла до» (ключевая концепция новой модели памяти Java, как указано в JSR-133) с любыми последующими чтениями одной и той же переменной. Это означает, что поток чтения должен иметь видимость во всем видимом для записывающего потока: то есть он должен видеть все переменные с не менее их «текущими» значениями во время записи.

Мы можем объяснить это в деталях, глядя на section 17.4.5 of the Java Language Specification, в частности, следующие ключевые моменты:

  1. «Если х и у являются действия одного и того же нити и х предшествует у в программном порядке, то ро (x, y) "(т. е. действия одного и того же потока не могут быть переупорядочены таким образом, чтобы они не соответствовали порядку программирования)
  2. « Запись в изменчивое поле (§8.3.1.4) происходит до каждого последующего чтения это поле ". (это поясняющий текст, объясняющий, что чтение-считывание летучего поля является точкой синхронизации)
  3. «Если hb (x, y) и hb (y, z), то hb (x, z)» (транзитивность происходит, перед тем)

Таким образом, в вашем примере:

  • записываемый в «сервис» (а) происходит, перед записью в «serviceReady» (б), в связи с правилом 1
  • запись в сервисный режим (b) происходит до того, как она считывается с тем же (c), из-за правила 2
  • поэтому, (a) происходит-до (c) (3-е правило)

означает, что вам гарантировано, что «сервис» установлен правильно, в этом случае, как только serviceReady является истинным.

Вы можете увидеть некоторые хорошие записи окна используя почти точно тот же самый пример, один на IBM DeveloperWorks - см «Новые гарантии для Летучего»:

значения

, которые были видны А в то время, V была написана гарантированно теперь будет видна B.

и один в the JSR-133 FAQ, написанной авторами этой JSR:

Таким образом, если читатель видит значение true для v, также гарантировано, что запись будет записана до 42. Это не было бы правдой при старой модели памяти. Если v не были волатильными, то компилятор мог изменить порядок записи в записи, а чтение читателем x могло бы увидеть 0.

+1

Вот почему мои глаза всегда пересекаются с JLS: мне показалось, что отношения «никогда раньше» не гарантируются нитями для нестабильной жизни. Спасибо за ссылку JSR-133, и мой ответ ушел. – kdgregory

+0

Если serviceReady не является изменчивым, кажется, что завершение «записи в службу» (a) происходит до того, как вы напишите в «serviceReady» (b) «из-за правила 1. Но на самом деле это не так. Поэтому я чувствую, что в трех правилах есть что-то, чего не хватает. Как вы думаете? – zwy

1

Вы правы о влиянии volatile, так что это должно быть правильным, но я запутался о вашем дизайне. Я не понимаю, почему вы не просто синхронизируете setService - это, вероятно, не будет вызываться часто. Если это называется более чем один раз, «if (serviceReady)» часть является спорной, так как он все равно будет верно, но это нормально, так как замена атомарная, если я правильно понимаю.

Полагаю, что service.doWork() является потокобезопасным, да?

+0

Это не настоящий код. В реальной системе я бы этого не делал, конечно :) Я придумал этот код только как пример обучения. –

2

AFAIK это правильный код.

@CPerkins: только синхронизируемый метод setService не будет работать, так как вам также нужно синхронизировать при чтении.

Однако одной переменной было бы достаточно в этом случае. Зачем вам нужно дополнительное логическое поле. .

private volatile Service service; 

public void setService(Service service) { 
    this.service = service; 
} 

public void doWork() { 
    if (service != null) { 
    service.doWork(); 
    } 
} 

при условии, что никто никогда не называет setService к null. Поэтому вам следует, вероятно, проверить нуль:

private volatile Service service; 

public void setService(Service service) { 
    if (service == null) throw NullPointerException(); 
    this.service = service; 
} 

public void doWork() { 
    if (service != null) { 
    service.doWork(); 
    } 
} 
+0

Да, совершенно верно, Кутци. Я написал в спешке, в основном отвечая на вопрос об изменчивости ... но я все еще спрашиваю: почему бы просто не синхронизировать? – CPerkins

+0

Да, я согласен с вами о дизайне. Но код изобретен, а не реальный. Вопрос о нестабильной семантике. –

+0

@ dotsid: Если у вас нет примера, который действительно нуждается в дополнительном нестабильном поле, то трудно понять, чего вы хотите достичь. – Kutzi

0

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

На практике это может сработать, в зависимости от реализации изменчивости используемой вами JVM. Если волатильные чтения выполняются путем сброса всех кэшей ЦП, он должен работать. Но я готов поспорить, что этого не произойдет. Can I force cache coherency on a multicore x86 CPU? хорошо читал эту тему.

Я бы сказал, что для этих двух переменных просто существует общий замок (java.util.concurrent.Lock или синхронизирован) и выполняться с ним.


Java Language Specification, Third Edition, это сказать о летучем:

8.3.1.4 Летучих Поля

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

и

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

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

В нем ничего не говорится о влиянии видимости других переменных.

+2

Извините, этот комментарий isn Иными словами, с Java 1.5. «Только волатильное чтение гарантирует, что поток чтения видит последнее значение переменной» неверен. Начиная с версии 1.5, волатильная запись «случится-до», если она последует за этой переменной, что означает, что все, что видимо для записывающего потока, теперь видно для потока чтения. См. http://www.ibm.com/developerworks/library/j-jtp03304/ в разделе «Новые гарантии для летучих» - в листинге 1 есть в основном именно этот пример. «значения, которые были видны А в момент написания V, теперь гарантированы, чтобы быть видимыми для В.» – Cowan

+0

@Cowan: Я проверил JLS 3rd edition и ничего не нашел по этому поводу. Не могли бы вы дать авторитетный источник для вашего заявления? –

+2

Роберт, это результат транзитивности отношения «бывает раньше». Когда serviceReady истинно, оно должно быть изменено с помощью setService(). Это устанавливается, прежде чем между изменчивой записью и чтением. Таким образом, действия перед записью и действия после чтения также связаны с отношением «случилось до». Другими словами, после того, как вы установили, что происходит до отношения некоторыми средствами синхронизации, ВСЕ изменения гарантированно будут видны. (17.4.5 Выполняется до заказа) –

Смежные вопросы