Пытаясь понять, как SubmissionPublisher
( исходный код в Java SE 10, OpenJDK | docs ), новый класс, добавленный в Java SE в версии 9, был реализован, я наткнулся на несколько вызовов API, о которых VarHandle
раньше не знал:
fullFence
, acquireFence
, releaseFence
, loadLoadFence
И storeStoreFence
.
Проведя некоторые исследования, особенно в отношении концепции барьеров / заборов памяти (я слышал о них ранее, да; но никогда не использовал их, поэтому был совершенно незнаком с их семантикой), я думаю, что у меня есть общее представление о том, для чего они , Тем не менее, поскольку мои вопросы могут возникнуть из-за неправильного представления, я хочу убедиться, что я правильно понял в первую очередь:
Барьеры памяти переупорядочивают ограничения относительно операций чтения и записи.
Барьеры памяти можно разделить на две основные категории: однонаправленные и двунаправленные барьеры памяти, в зависимости от того, устанавливают ли они ограничения на чтение или запись, или на то и другое.
C ++ поддерживает множество барьеров памяти , однако они не совпадают с теми, которые предоставляет
VarHandle
. Однако некоторые из имеющихся в наличии барьеров памятиVarHandle
обеспечивают эффекты упорядочения , совместимые с соответствующими им барьерами памяти C ++.#fullFence
совместим сatomic_thread_fence(memory_order_seq_cst)
#acquireFence
совместим сatomic_thread_fence(memory_order_acquire)
#releaseFence
совместим сatomic_thread_fence(memory_order_release)
#loadLoadFence
и не#storeStoreFence
имеют совместимого контраргумента C ++
Слово « совместимость» кажется здесь действительно важным, поскольку семантика явно отличается, когда дело доходит до деталей. Например, все барьеры C ++ являются двунаправленными, в то время как барьеры Java не являются (обязательно).
- Большинство барьеров памяти также имеют эффекты синхронизации. Это особенно зависит от используемого типа барьера и ранее выполненных инструкций барьера в других потоках. Поскольку полное значение, которое имеет инструкция для барьера, зависит от аппаратного обеспечения, я буду придерживаться барьеров более высокого уровня (C ++). Например, в C ++ изменения, сделанные до инструкции барьера освобождения , видны потоку, выполняющему инструкцию барьера получения .
Верны ли мои предположения? Если да, то мои следующие вопросы:
Существуют ли барьеры памяти в
VarHandle
какой-либо синхронизации памяти?Независимо от того, вызывают ли они синхронизацию памяти или нет, для чего могут быть полезны ограничения переупорядочения в Java? Модель памяти Java уже дает некоторые очень строгие гарантии в отношении упорядочения, когда задействованы изменчивые поля, блокировки или
VarHandle
подобные операции#compareAndSet
.
В случае, если вы ищете пример: вышеупомянутый BufferedSubscription
, внутренний класс SubmissionPublisher
(источник связан с выше), установил полный забор в строке 1079 (функция growAndAdd
; поскольку связанный веб-сайт не поддерживает идентификаторы фрагмента, просто CTRL + F для него ). Однако для меня неясно, для чего это.
plain -> opaque -> release/acquire -> volatile (sequential consistency)
.Ответы:
На самом деле это в основном не ответ (изначально хотелось сделать это комментарием, но, как вы видите, это слишком долго). Просто я сам много сомневался в этом, много читал и исследовал, и на данный момент могу с уверенностью сказать: это сложно. Я даже написал несколько тестов для jcstress, чтобы выяснить, как они работают (глядя на сгенерированный код сборки) и хотя некоторые из них так или иначе смысл, тема в общем-то далеко не простая.
Самое первое, что вам нужно понять:
Эта работа в процессе.
Во-вторых, если вы действительно хотите поцарапать поверхность здесь, это самое первое, что нужно посмотреть . Разговор невероятный. Моя любимая часть - когда Херб Саттер поднимает свои 5 пальцев и говорит: «Вот так много людей могут действительно и правильно работать с ними». Это должно дать вам подсказку о сложности. Тем не менее, есть некоторые тривиальные примеры, которые легко понять (например, счетчик, обновленный несколькими потоками, который не заботится о других гарантиях памяти, а заботится только о том, чтобы он сам увеличивался правильно).
Другой пример - когда (в Java) вы хотите, чтобы
volatile
флаг управлял потоками для остановки / запуска. Вы знаете, классическийЕсли вы работаете с Java, вы бы знали, что без
volatile
этого код нарушается (например, вы можете прочитать, почему блокировка двойной проверки нарушается без него). Но знаете ли вы, что для некоторых людей, которые пишут высокопроизводительный код, это слишком много?volatile
чтение / запись также гарантирует последовательную согласованность - у этого есть некоторые сильные гарантии, и некоторые люди хотят более слабую версию этого.И вы спросите, зачем кому-то это может понадобиться, например? Не все заинтересованы во всех изменениях, которые поддерживаются
volatile
.Посмотрим, как мы этого добьемся в Java. Прежде всего, такие экзотические вещи уже существовали в API
AtomicInteger::lazySet
. Это не определено в модели памяти Java и не имеет четкого определения ; все еще люди использовали это (LMAX, afaik или это для большего чтения ). ИМХО,AtomicInteger::lazySet
естьVarHandle::releaseFence
(илиVarHandle::storeStoreFence
).Давайте попробуем ответить, зачем это кому-то нужно ?
У JMM есть два основных способа доступа к полю: простой и изменчивый (что гарантирует последовательную согласованность ). Все эти методы, о которых вы упомянули, существуют для того, чтобы что-то промежуточное между ними - семантикой освобождения / приобретения ; Полагаю, есть случаи, когда людям это действительно нужно.
Еще большее расслабление от выпуска / приобретения было бы непрозрачным , что я все еще пытаюсь полностью понять .
Таким образом, суть (ваше понимание довольно правильное, кстати): если вы планируете использовать это в Java - у них нет спецификаций на данный момент, делайте это на свой страх и риск. Если вы действительно хотите понять их, то их стартовые режимы в C ++ - это то, что вам нужно.
источник
lazySet
, ссылаясь на древние ответы, текущая документация точно говорит, что это означает, в настоящее время. Кроме того, вводить в заблуждение тот факт, что JMM имеет только два режима доступа. У нас есть изменчивое чтение и изменчивое запись , которые вместе могут установить отношения « до того как случится» .volatile
ключевым словом была C99, пять лет спустя после Java, но ей все еще не хватало полезной семантики, даже в C ++ 03 нет модели памяти. Вещи, которые C ++ называет «атомарными», также намного моложе, чем Java. Иvolatile
ключевое слово даже не подразумевает атомных обновлений. Так почему это должно быть названо так.restrict
, однако, я помню времена, когда мне приходилось писать,__volatile
чтобы использовать расширение компилятора без ключевых слов. Так что, возможно, он не реализовал C89 полностью? Не говори мне, что я такой старый. До Java 5 онvolatile
был намного ближе к C. Но в Java не было MMIO, поэтому его целью всегда была многопоточность, но семантика до Java 5 была не очень полезна для этого. Таким образом, была добавлена семантика релиза / приобретения, но, тем не менее, она не является атомарной (атомарные обновления - это дополнительная функция, встроенная в нее).