Пункты памяти и стиль кодирования по Java VM

Предположим, у меня есть статический сложный объект, который периодически обновляется пулом streamов и читается более или менее непрерывно в длинном streamе. Сам объект всегда неизменен и отражает последнее состояние чего-то.

class Foo() { int a, b; } static Foo theFoo; void updateFoo(int newA, int newB) { f = new Foo(); fa = newA; fb = newB; // HERE theFoo = f; } void readFoo() { Foo f = theFoo; // use f... } 

Меня нисколько не волнует, видит ли мой читатель старый или новый Foo, однако мне нужно увидеть полностью инициализированный объект. IIUC, спецификация Java говорит, что без барьера памяти в ЗДЕСЬ я могу увидеть объект с инициализированным fb, но fa еще не зафиксирован в памяти. Моя программа – это настоящая программа, которая рано или поздно переносит материал в память, поэтому мне не нужно сразу передавать новое значение theFoo в память (хотя это не помешает).

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

Как вы могли бы написать его так, чтобы он был максимально читабельным?
Бонусная наgradleа за версию Scala 🙂

Краткие ответы на исходный вопрос

  • Если Foo неизменен, просто создание полей final обеспечит полную инициализацию и постоянную видимость полей для всех streamов независимо от синхронизации.
  • Независимо от того, является ли Foo неизменным, публикация через volatile theFoo или AtomicReference theFoo достаточна для обеспечения того, чтобы записи в его поля были видны для любого streamа, считываемого с помощью ссылки theFoo
  • Используя обычное назначение для theFoo , нити читателей никогда не гарантированно будут видеть какое-либо обновление
  • По моему мнению, и на основе JCiP, «наиболее читаемым способом реализации барьера памяти» является AtomicReference , при этом явная синхронизация идет в секунду, а использование volatile вступает в третье
  • К сожалению, мне нечего предложить в Scala

Вы можете использовать volatile

Я виню тебя. Теперь я подключен, я разбил JCiP , и теперь мне интересно, правильный ли код, который я когда-либо писал. Фрагмент кода выше, по сути, потенциально несовместим. (Изменить: см. Раздел ниже «Безопасная публикация с помощью volatile.»). В streamе чтения также можно увидеть устаревшие (в этом случае любые значения по умолчанию для a и b ) для неограниченного времени. Вы можете сделать одно из следующих действий, чтобы ввести крайний край:

  • Публикация с помощью volatile , которая создает предыдущий край, эквивалентный monitorenter (стороне чтения) или monitorexit (запись)
  • Используйте final поля и инициализируйте значения в конструкторе перед публикацией
  • Ввести синхронизированный блок при записи новых значений в объект theFoo
  • Использовать поля AtomicInteger

Это позволяет упорядочить порядок записи (и решает проблемы их видимости). Затем вам нужно обратиться к видимости новой ссылки theFoo . Здесь volatile уместна – JCiP говорит в разделе 3.1.4 «Неустойчивые переменные» (и здесь переменная – это theFoo ):

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

  • Запись в переменную не зависит от ее текущего значения, или вы можете убедиться, что только один stream когда-либо обновляет значение;
  • Переменная не участвует в инвариантах с другими переменными состояния; а также
  • Блокировка не требуется по какой-либо другой причине во время обращения к переменной

Если вы сделаете следующее, вы будете золотыми:

 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 для индикаторов о конкретных поведениях, для которых потребуется документация и ссылки на соответствующие ссылки. Дуг Ли также подготовил список проблем, которые следует учитывать при документировании поведения параллелизма . Документация уместна, в частности, из-за беспокойства, скептицизма и путаницы в вопросах параллелизма (на SO: «Был ли слишком циничный параллелизм java?» ). Кроме того, такие инструменты, как FindBugs , теперь предоставляют правила статической проверки, чтобы заметить нарушения семантики annotations JCiP, такие как «Непоследовательная синхронизация: IS_FIELD-NOT_GUARDED» .

До тех пор, пока вы не подумаете, что у вас есть причина в противном случае, лучше всего перейти к наиболее читаемому решению, что-то вроде этого (спасибо, @Burleigh Bear), используя annotations @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 theFoo; void updateFoo(final int newA, final int newB) { theFoo.set(new Foo(newA,newB)); } void readFoo() { Foo f = theFoo.get(); ... } 

Когда целесообразно использовать volatile

Во-первых, обратите внимание, что этот вопрос относится к вопросу здесь, но был рассмотрен много, много раз на SO:

  • Когда вы используете волатильность?
  • Вы когда-нибудь использовали ключевое слово volatile в Java?
  • Для того, что используется “volatile”
  • Использование ключевого слова volatile
  • Java volatile boolean vs. AtomicBoolean

Фактически, поиск google: «site: stackoverflow.com + java + volatile + keyword» возвращает 355 отличных результатов. Использование volatile – это, в лучшем случае, неустойчивое решение. Когда это уместно? JCiP дает некоторые абстрактные указания (цитируется выше). Здесь я найду еще несколько практических рекомендаций:

  • Мне нравится этот ответ : « volatile можно использовать для безопасного опубликования неизменяемых объектов», который аккуратно инкапсулирует большую часть диапазона использования, которое можно ожидать от программиста приложения.
  • Ответ @ mdma здесь : « volatile наиболее полезен в алгоритмах без блокировки» суммирует другой class использования – специальные, блокирующие алгоритмы, которые достаточно чувствительны к производительности, чтобы заслужить тщательный анализ и проверку экспертом.
  • Безопасная публикация через изменчивый

    Следуя за @Jed Wesley-Smith , похоже, что volatile теперь обеспечивает более надежные гарантии (с JSR-133), и более раннее утверждение «Вы можете использовать volatile при условии, что опубликованный объект является неизменным» достаточно, но, возможно, не обязательно.

    Рассматривая FAQ JMM, две записи. Как работают последние поля под новым JMM? и что делает volatile? на самом деле не справляются вместе, но я думаю, что второй дает нам то, что нам нужно:

    Разница в том, что теперь уже не так легко переупорядочивать нормальные обращения к полям вокруг них. Запись в энергозависимое поле имеет тот же эффект памяти, что и релиз монитора, а чтение из энергозависимого поля имеет тот же эффект памяти, что и монитор. Фактически, поскольку новая модель памяти накладывает более строгие ограничения на переупорядочивание волатильных полевых доступов с другими доступными полями, волатильными или нет, все, что было видимо для streamа A, когда оно записывает в изменчивое поле f, становится видимым для streamа B, когда оно читает f.

    Замечу, что, несмотря на несколько перечитаний JCiP, соответствующий текст там не перескакивал, пока Джед не указал на это. Это на стр. 38, раздел 3.1.4, и он говорит более или менее то же, что и предыдущая цитата – опубликованный объект должен быть только эффективно неизменным, не требуется никаких final полей, QED.

    Старые вещи, предназначенные для подотчетности

    Один комментарий: Любая причина, по которой newA и newB не могут быть аргументами для конструктора? Тогда вы можете положиться на правила публикации для конструкторов …

    Кроме того, использование AtomicReference вероятно, устраняет любую неопределенность (и может купить вам другие преимущества в зависимости от того, что вам нужно сделать в остальной части classа …) Кроме того, кто-то умнее меня может сказать вам, но мне всегда кажется загадочным …

    В дальнейшем обзоре я считаю, что комментарий от @Burleigh Bear выше правилен — (EDIT: см. Ниже), вам действительно не нужно беспокоиться об упорядочивании вне очереди, поскольку вы публикуете новый объект для theFoo . В то время как в другом streamе можно было бы увидеть противоречивые значения для newA и newB как описано в JLS 17.11, этого не может быть здесь, потому что они будут переданы в память до того, как другой stream получит ссылку на новый f = new Foo() экземпляр f = new Foo() вы создали … это безопасная разовая публикация. С другой стороны, если вы написали

     void updateFoo(int newA, int newB) { f = new Foo(); theFoo = f; fa = newA; fb = newB; } 

    Но в этом случае проблемы синхронизации достаточно прозрачны, и упорядочение – это наименьшее из ваших забот. Для некоторых полезных рекомендаций по летучести ознакомьтесь с этой статьей developerWorks .

    Однако у вас может возникнуть проблема, когда отдельные streamи чтения могут видеть старое значение для theFoo для неограниченного количества времени. На практике это редко случается. Однако JVM может быть разрешено кэшировать значение ссылки theFoo в контексте другого streamа. Я совершенно уверен, что маркировка theFoo мере theFoo как volatile будет обращаться к этому, как и любой синхронизатор или AtomicReference .

    Наличие неизменяемого Foo с конечными полями a и b решает проблемы видимости со значениями по умолчанию, но делает это также неустойчивым.

    Лично мне нравится иметь неизменяемые classы ценностей, так как они намного сложнее использовать.

  • Является ли stl-вектор одновременным чтением streamобезопасным?
  • В чем разница между ConcurrentHashMap и Collections.synchronizedMap (Карта)?
  • Итерирует ли ConcurrentHashMap значение streamобезопасности?
  • Есть ли способ для нескольких процессов совместно использовать прослушивающий сокет?
  • Является ли это идиоматическим пулом рабочих streamов в Go?
  • Как дождаться завершения всех streamов, используя ExecutorService?
  • .NET Асинхронный stream чтения / записи
  • Более эффективный способ для цикла паузы
  • вопрос о примере «Java Concurrency in Practice»
  • Почему назначение целых чисел на естественно выровненной переменной атома на x86?
  • Неустойчиво дорого?
  • Interesting Posts

    Почему это имя с подчеркиванием не совместимо с CLS?

    как проверить начало строки в C ++

    «Сервер Tomcat v7.0 Сервер на локальном хосте не смог запустить» без трассировки стека, когда он работает в терминале

    Что, * конкретно * удаляет и сохраняет Windows 8 «Обновить ваш компьютер»?

    Android WebView не загружает URL-адрес HTTPS

    Tar Невозможно открыть: нет такого файла или каталога

    Внедрение комет для ASP.NET?

    Как заменить жесткий диск, который находится в двухстороннем зеркальном хранилище в Windows 8?

    Угловой 2 – внутренний стиль HTML

    Как обнаружить изменение состояния Bluetooth с помощью широковещательного приемника?

    Можно ли выбрать текстовый блок WPF?

    Как настроить яркость подсветки монитора в Windows

    currentjs внутренний объект, что есть “_d” против “_i”

    Почему добавление нового значения в список перезаписывает предыдущие значения в списке

    Панель инструментов JQGrid Поиск: поиск нескольких слов для столбца

    Давайте будем гением компьютера.