Короткие ответы на оригинальный вопрос
- Если
Foo
является неизменным, простое окончательное заполнение полей обеспечит полную инициализацию и согласованную видимость полей для всех потоков независимо от синхронизации.
- Независимо от того, является ли
Foo
неизменным, публикации через volatile theFoo
или AtomicReference<Foo> theFoo
достаточно, чтобы гарантировать, что записи в его поля видны любому потоку, читающему через theFoo
ссылку
- Используя простое присвоение
theFoo
, потоки читателей: никогда гарантированно не увидят обновления
- По моему мнению, и на основе JCiP «наиболее читаемый способ реализации барьера памяти» - это
AtomicReference<Foo>
, с явной синхронизацией, идущей за секунду, и использованием volatile
, идущей за третьим
- К сожалению, мне нечего предложить в Scala
Вы можете использовать volatile
Я виню тебя. Теперь я подключен, у меня отключен JCiP , и теперь я задаюсь вопросом, верен ли какой-либо написанный мной код. Фрагмент кода выше, на самом деле, потенциально противоречив. (Изменить: см. Ниже раздел «Безопасная публикация через volatile».) Поток чтения может также видеть устаревшие (в этом случае, какими бы ни были значения по умолчанию для a
и b
) для неограниченное время. Вы можете выполнить одно из следующих действий, чтобы ввести крайний случай до:
- Публикация через
volatile
, в результате чего создается край перед событием, эквивалентный monitorenter
(сторона чтения) или monitorexit
(сторона записи)
- Используйте
final
поля и инициализируйте значения в конструкторе перед публикацией
- Ввести синхронизированный блок при записи новых значений в
theFoo
объект
- Использовать
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 для индикаторов о конкретных поведениях, которые могли бы требовать документацию и ссылки на соответствующие ссылки. Даг Ли также подготовил список вопросов, которые следует учитывать при документировании поведения параллелизма . Документация уместна, особенно из-за беспокойства, скептицизма и путаницы, связанных с проблемами параллелизма (на SO: "Не зашел ли цинизм параллелизма Java слишком далеко?" ). Кроме того, такие инструменты, как 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:
Фактически, поиск в Google: "site: stackoverflow.com + java + volatile + ключевое слово" возвращает 355 различных результатов. Использование volatile
в лучшем случае является нестабильным решением. Когда это уместно? JCiP дает некоторое абстрактное руководство (упомянутое выше). Я соберу несколько практических рекомендаций здесь:
Мне нравится
этот ответ : «
volatile
может использоваться для безопасной публикации неизменяемых объектов», который аккуратно заключает в себе большую часть диапазона использования, которого можно ожидать от прикладного программиста.
@ mdma's
ответ здесь : «
volatile
наиболее полезен в алгоритмах без блокировок», обобщает другой класс применений - алгоритмы специального назначения без блокировок, которые достаточно чувствительны к производительности, чтобы заслуживать тщательного анализа и проверка экспертом.
Безопасная публикация через volatile
Вслед за @ Джедом Уэсли-Смитом представляется, что volatile
теперь предоставляет более строгие гарантии (начиная с JSR-133) и более раннее утверждение "Вы можете использовать volatile
при условии публикации объекта является неизменным "достаточно, но, возможно, не обязательно.
Глядя на FAQ по JMM, две записи Как конечные поля работают в новом JMM? и Что делает volatile? на самом деле не рассматриваются вместе, но я думаю, второе дает нам то, что нам нужно:
Разница в том, что сейчас нет
дольше так легко изменить нормальное поле
доступ вокруг них. Запись в
энергозависимое поле имеет ту же память
эффект как выпуск монитора, и
чтение из изменчивого поля имеет
тот же эффект памяти, что и у монитора
приобретать. По сути, потому что новый
модель памяти местами строже
ограничения на изменение порядка летучих
доступ к полю с другим полем
доступ, изменчивый или нет, что-нибудь
что было видно нити А, когда это
пишет в летучее поле F становится
видимый потоку B, когда он читает f.
Я отмечу, что, несмотря на несколько перечитываний JCiP, соответствующий текст там не выпал мне, пока Джед не указал на это. Это на с. 38, раздел 3.1.4, и в нем сказано более или менее то же самое, что и в предыдущей цитате - опубликованный объект должен быть только эффективно неизменяемым, поля final
не требуются, QED.
Старые вещи, сохраняемые для ответственности
Один комментарий: Есть ли причина, по которой newA
и newB
не могут быть аргументами для конструктора? Тогда вы можете положиться на правила публикации для конструкторов ...
Кроме того, использование AtomicReference
, вероятно, устранит любую неопределенность (и может принести вам другие выгоды в зависимости от того, что вам нужно сделать в остальной части класса ...) Кроме того, кто-то умнее меня может сказать вам, если volatile
решил бы это, но мне всегда кажется загадочным ...
В дальнейшем рассмотрении, я считаю, что комментарий от @Burleigh Bear выше является правильным --- (РЕДАКТИРОВАТЬ: см. Ниже) Вам на самом деле не нужно беспокоиться о неупорядоченном порядке, так как вы публикуем новый объект для 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;
}
Но в этом случае проблемы с синхронизацией довольно прозрачны, и упорядочение - это не ваше беспокойство. Некоторые полезные рекомендации по volatile можно найти в этой статье developerWorks .
Однако у вас может быть проблема, когда отдельные потоки читателей могут видеть старое значение для theFoo
в течение неограниченного времени. На практике такое случается редко. Однако JVM может быть разрешено кэшировать значение ссылки theFoo
в контексте другого потока. Я совершенно уверен, что пометка theFoo
как volatile
решит эту проблему, как и любой другой синхронизатор или AtomicReference
.