Ответ 1
Краткие ответы на исходный вопрос
- Если
Foo
является неизменным, просто создание полей final гарантирует полную инициализацию и постоянную видимость полей для всех потоков независимо от синхронизации. - Независимо от того, является или нет
Foo
неизменным, публикация черезvolatile theFoo
илиAtomicReference<Foo> theFoo
достаточна для того, чтобы записи в его поля были видны для любого чтения нитей с помощьюtheFoo
reference - Используя простое назначение
theFoo
, нити читателей никогда не гарантируют, что вы увидите какое-либо обновление. - По моему мнению, и на основе JCiP, "наиболее читаемый способ реализации барьера памяти" -
AtomicReference<Foo>
, с явной синхронизацией, входящей в секунду, и использованиеvolatile
, входящего в третью - К сожалению, мне нечего предложить в Scala
Вы можете использовать volatile
Я обвиняю вас. Теперь я подключен, я разбил JCiP, и теперь мне интересно, правильно ли написан какой-либо код, который я когда-либо писал. Фрагмент кода выше, по сути, потенциально несовместим. (Изменить: см. Раздел ниже в разделе Безопасная публикация с помощью volatile.) В потоке чтения также можно увидеть устаревшие (в данном случае любые значения по умолчанию для Вы можете сделать одно из следующих действий, чтобы представить крайний край:a
и b
) для неограниченного времени.
- Опубликовать через
volatile
, который создает резидентный фронт, эквивалентныйmonitorenter
(стороне чтения) илиmonitorexit
(сторона записи) - Используйте поля
final
и инициализируйте значения в конструкторе перед публикацией - Представьте синхронизированный блок при записи новых значений в
theFoo
object - Использовать поля
AtomicInteger
Это позволяет упорядочить порядок записи (и решает проблемы их видимости). Затем вам нужно рассмотреть видимость новой ссылки theFoo
. Здесь volatile
подходит - JCiP говорит в разделе 3.1.4 "Неустойчивые переменные" (и здесь переменная theFoo
):
Вы можете использовать изменчивые переменные только при выполнении всех следующих критериев:
- Записывает переменную, не зависящую от ее текущего значения, или вы можете убедиться, что только один поток когда-либо обновляет значение;
- Переменная не участвует в инвариантах с другими переменными состояния; и
- Блокировка не требуется по какой-либо другой причине во время доступа к переменной
Если вы сделаете следующее, вы будете золотыми:
class Foo {
// it turns out these fields may not be final, with the volatile publish,
// the values will be seen under the new JMM
final int a, b;
Foo(final int a; final int b)
{ this.a = a; this.b=b; }
}
// without volatile here, separate threads A' calling readFoo()
// may never see the new theFoo value, written by thread A
static volatile Foo theFoo;
void updateFoo(int newA, int newB) {
f = new Foo(newA,newB);
theFoo = f;
}
void readFoo() {
final Foo f = theFoo;
// use f...
}
Простой и читаемый
Несколько человек на этом и других потоках (спасибо @John V) отмечают, что власти по этим вопросам подчеркивают важность документирования поведения синхронизации и допущений. JCiP подробно рассказывает об этом, предоставляет набор аннотаций, который можно использовать для документирования и статической проверки, а также вы можете посмотреть на JMM Cookbook для индикаторов о конкретных поведениях, которые потребуют документации и ссылок на соответствующие ссылки. Дуг Ли также подготовил список вопросов для рассмотрения, когда документирует поведение concurrency. Документация уместна, в частности, из-за беспокойства, скептицизма и путаницы вокруг проблем concurrency (на SO: "Был ли java concurrency цинизм зашел слишком далеко?" ). Кроме того, инструменты, такие как FindBugs, теперь предоставляют правила статической проверки, чтобы заметить нарушения семантики аннотации JCiP, например "Непоследовательная синхронизация: IS_FIELD-NOT_GUARDED" .
До тех пор, пока вы не подумаете, что у вас есть причина в противном случае, лучше всего перейти к наиболее читаемому решению, что-то вроде этого (спасибо, @Burleigh Bear), используя @Immutable
и @GuardedBy
аннотации.
@Immutable
class Foo {
final int a, b;
Foo(final int a; final int b) { this.a = a; this.b=b; }
}
static final Object FooSync theFooSync = new Object();
@GuardedBy("theFooSync");
static Foo theFoo;
void updateFoo(final int newA, final int newB) {
f = new Foo(newA,newB);
synchronized (theFooSync) {theFoo = f;}
}
void readFoo() {
final Foo f;
synchronized(theFooSync){f = theFoo;}
// use f...
}
или, возможно, поскольку он чище:
static AtomicReference<Foo> theFoo;
void updateFoo(final int newA, final int newB) {
theFoo.set(new Foo(newA,newB)); }
void readFoo() { Foo f = theFoo.get(); ... }
Когда это целесообразно использовать volatile
Во-первых, обратите внимание, что этот вопрос относится к вопросу здесь, но был рассмотрен много раз на SO:
- Когда вы используете volatile?
- Вы когда-нибудь использовали ключевое слово volatile в Java.
- Для использования "volatile"
- Использование ключевого слова volatile
- Java volatile boolean vs. AtomicBoolean
Фактически поиск google: "site: stackoverflow.com + java + volatile + keyword" возвращает 355 различных результатов. Использование volatile
- это, в лучшем случае, неустойчивое решение. Когда это уместно? JCiP дает некоторые абстрактные указания (цитируется выше). Здесь я найду еще несколько практических рекомендаций:
volatile
можно использовать для безопасного опубликования неизменяемых объектов", который аккуратно инкапсулирует большую часть диапазона использования, который можно ожидать от прикладного программиста.
@mdma ответьте здесь: "volatile
наиболее полезен в алгоритмах без блокировки" суммирует другой класс использования - специальные алгоритмы блокировки, которые достаточно высокая производительность, чтобы заслужить тщательный анализ и проверку экспертом.
Безопасная публикация через изменчивый
Следуя @Jed Wesley-Smith, кажется, что volatile
теперь обеспечивает более сильные гарантии (с JSR-133) и более раннее утверждение "Вы можете использование volatile
при условии, что опубликованный объект является неизменным" достаточно, но, возможно, не нужно.
Глядя на FAQ JMM, две записи Как работают последние поля под новым JMM? и Что делает volatile do? на самом деле не справляются вместе, но я думаю, что второй дает нам то, что нам нужно:
Разница в том, что сейчас нет дольше, так легко переупорядочить нормальное поле доступ вокруг них. Письмо в поле volatile имеет одинаковую память эффект как релиз монитора, и чтение из изменчивого поля имеет тот же эффект памяти, что и монитор приобретать. Фактически, поскольку новый модель памяти более жесткая ограничения на переупорядочивание изменчивых доступ к полям с другим полем доступ, неустойчивость или нет, что-либо который был виден в потоке A, когда он пишет в неустойчивое поле f видимый для потока B, когда он читает f.
Отмечу, что, несмотря на несколько перечитаний JCiP, соответствующий текст там не перескакивал, пока Джед не указал на это. Это на стр. 38, раздел 3.1.4, и он говорит более или менее то же, что и предыдущая цитата - опубликованный объект должен быть только эффективно неизменным, не требуются поля final
, QED.
Старые вещи, предназначенные для подотчетности
Один комментарий: Любая причина, по которой newA
и newB
не могут быть аргументами для конструктора? Тогда вы можете полагаться на правила публикации для конструкторов...
Кроме того, использование AtomicReference
, вероятно, устраняет любую неопределенность (и может купить вам другие преимущества в зависимости от того, что вам нужно сделать в остальной части класса...) Кроме того, кто-то умнее меня может сказать вам, если volatile
решит это, но всегда кажется загадочным для меня...
В дальнейшем обзоре я считаю, что комментарий от @Burleigh Bear выше правилен --- (EDIT: см. ниже) вам действительно не нужно беспокоиться о заказе вне очереди, поскольку вы публикуют новый объект theFoo
. В то время как другой поток, возможно, мог видеть несогласованные значения для newA
и newB
, как описано в JLS 17.11, этого здесь не может быть, потому что они будут переданы в память до того, как другой поток получит ссылку на новый f = new Foo()
экземпляр, который вы создали... это безопасная разовая публикация. С другой стороны, если вы написали
void updateFoo(int newA, int newB) {
f = new Foo(); theFoo = f;
f.a = newA; f.b = newB;
}
Забастовкa > Но в этом случае проблемы синхронизации достаточно прозрачны, и упорядочение - это наименьшее из ваших забот. Для некоторых полезных рекомендаций по volatile, ознакомьтесь с этой статьей developerWorks.
Однако у вас может возникнуть проблема, когда отдельные потоки чтения могут видеть старое значение для theFoo
для неограниченного количества времени. На практике это редко случается. Однако JVM может быть разрешено кэшировать значение ссылки theFoo
в другом контексте потока. Я уверен, что маркировка theFoo
, поскольку volatile
будет решать это, как и любой тип синхронизатора или AtomicReference
.