Что я должен использовать в качестве барьера памяти для коллекции?

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

Используя барьер памяти, это должно быть легко. Но как я могу создать такой барьер памяти, который влияет на все внутренние переменные/состояния-члены экземпляра ArrayList? Я знаю, что синхронизация возможна, но я не знаю, на каком объекте я должен синхронизироваться, чтобы добиться желаемого эффекта.

Есть ли официальная ссылка на эту проблему, определяющая передовой опыт?

Мне кажется, что наиболее простым способом достижения барьера памяти является вызов чего-то вроде fullFence(). Но похоже, что это не рекомендуемый способ ;-)


person ideaboxer    schedule 05.05.2016    source источник


Ответы (3)


Синхронизация работает до тех пор, пока вы синхронизируете один и тот же объект при чтении и записи, она гарантированно будет согласованной. Я рекомендую взять Java Concurrency In Practic для более глубокого погружения в параллелизм.

В вашем случае конец потока обеспечивает барьер памяти, поэтому должно быть безопасно читать любые значения, которые могли быть изменены первым потоком. См. https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html.

The final action in a thread T1 synchronizes-with any action in another thread T2 that detects that T1 has terminated.

T2 may accomplish this by calling T1.isAlive() or T1.join().
person mvd    schedule 05.05.2016
comment
Учитывая, что в моем коде есть обратный вызов Scala Future {...}.onComplete {...} (обратный вызов onComplete вызывается, когда заканчивается поток Future, а в обратном вызове onComplete второй поток запускается вторым Future: правда ли, что обратный вызов вызывается после заканчивается первый поток Future, или он вызывается до завершения? - person ideaboxer; 05.05.2016

Я могу быть уверен, что второй поток выполняется после завершения первого потока.

Давайте назовем ваши два потока A и B, где поток B — это тот, который произвел результат, а поток A — это более продолжительный поток, который хочет использовать результат.

Вашему потоку A нужно только вызвать B.join(). Все, что поток B записал в память до того, как он умер, гарантированно станет видимым для потока A после возврата из вызова join().


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


Третий подход заключается в использовании переменной volatile. Вы говорите, что рассматриваемые данные не могут быть объявлены volatile, но доступ к volatile-переменной влияет не только на саму переменную. Если поток B обновляет данные, а затем обновляет некоторые volatile int i;, тогда все, что B записал в память до обновления i, станет видимым для A после того, как A прочитает i.

person Solomon Slow    schedule 05.05.2016

То, что вам действительно нужно, происходит раньше, чем в списке. Полное ограждение в конце обновления состояния не гарантирует, что читатель увидит согласованные, полностью обновленные значения в списке, например. потому что некоторые операции чтения застревают в основной очереди недействительности или потому что JIT кэширует некоторые значения в регистрах, потому что VM не знает, что вы имеете дело с общими данными, поэтому код может вести себя не так, как вы ожидаете. Если вы не боитесь JIT (может быть, потому что вы знаете, что каким-то образом перечитываете список из памяти), то full-fence будет достаточно только на архитектуре x86, потому что x86 имеет свойство TSO (общий порядок хранения), поэтому барьеры ( ~fences), необходимые для чтения общих данных (LoadLoad и LoadStore), фактически не работают< /а>. Но заборы и барьеры не являются частью JMM или какого-либо публичного API, поэтому они нам недоступны (кроме Unsafe). Пока JMM дает вам гарантии правильности только для правильное выполнение (с использованием отношений "происходит до" и "синхронизировано с"), вы должны полагаться на эту механику, а не на хитроумный Unsafe.

Чтобы обеспечить связь «происходит до» без синхронизации, вы можете добавить дополнительную изменчивую переменную и записать в нее в конце обновления и прочитать из нее перед чтением из списка (запись в изменчивую переменную происходит до чтения записанного значения из этой переменной). Или вы можете просто синхронизировать свой экземпляр списка с тем же эффектом (освобождение монитора происходит до приобретения того же монитора) + взаимное исключение.

person qwwdfsad    schedule 05.05.2016