Важно не эффект забора, а эффект времени компиляции, заставляющий компилятор перезагружать вещи.
Ваш t1
l oop не содержит volatile
операций чтения или чего-либо еще, что могло бы синхронизироваться с другим потоком, поэтому нет гарантии, что он когда-либо заметит любые изменения любых переменных. т.е. при JITing в asm компилятор может сделать al oop, который загружает значение в регистр один раз, вместо того, чтобы каждый раз перезагружать его из памяти. Это тот тип оптимизации, который вы всегда хотите, чтобы компилятор мог выполнять для не общих данных, поэтому в языке есть правила, которые позволяют ему это делать, когда невозможна синхронизация.
И, конечно же, условие можно поднять из л oop. Так что без барьеров или чего-либо, ваш читатель l oop может JIT в asm, который реализует эту логику c:
if(t.flag) {
for(;;){} // infinite loop
}
Помимо упорядочения, другая часть Java volatile
- это предположение о том, что другие потоки могут изменять его асинхронно, поэтому нельзя считать, что множественные чтения дают одно и то же значение.
Но unsafe.loadFence();
делает перезагрузку JVM t.flag
из (кэш-когерентной Запоминание каждой итерации. Я не знаю, требуется ли это Java spe c или просто деталью реализации, которая заставляет его работать.
Если это был C ++ с переменной не atomic
(которая было бы неопределенное поведение в C ++), вы бы увидели точно такой же эффект в компиляторе, как G CC. _mm_lfence
также будет полным барьером во время компиляции, а также выдаст бесполезную инструкцию lfence
, эффективно сообщающую компилятору, что вся память могла измениться и, следовательно, должна быть перезагружена. Поэтому он не может переупорядочивать нагрузки или выводить их из циклов.
Кстати, я бы не был уверен, что unsafe.loadFence()
даже JIT соответствует инструкции lfence
на x86. Это бесполезно для упорядочения памяти (за исключением очень непонятных вещей, таких как ограждение загрузок NT из памяти W C, например, копирование из видеопамяти RAM, что JVM может предположить, что не происходит), поэтому JITM JITM для x86 это можно рассматривать как барьер времени компиляции. Точно так же, как компиляторы C ++ делают для std::atomic_thread_fence(std::memory_order_acquire);
- переупорядочение блоков во время компиляции нагрузок через барьер, но не выдает asm-инструкций, потому что asm-память хоста, на котором работает JVM, уже достаточно сильна.
В теме 2 unsafe.fullFence();
я считаю бесполезным . Это просто заставляет поток ждать, пока более ранние хранилища не станут глобально видимыми, прежде чем могут произойти более поздние загрузки / хранилища. t.flag = false;
- это видимый побочный эффект, который нельзя оптимизировать, поэтому в асимметричном JIT-файле определенно происходит, есть ли за ним барьер или нет, даже если это не volatile
. И его нельзя отложить или объединить с чем-то другим, потому что в этом же потоке больше ничего нет.
Хранилища Asm всегда становятся видимыми для других потоков, вопрос только в том, ждет ли текущий поток своего буфера хранения, чтобы слить или нет, прежде чем делать больше вещей (особенно нагрузок) в этой теме. т.е. предотвратить все переупорядочения, включая StoreLoad. Java volatile
делает это, как C ++ memory_order_seq_cst
(используя полный барьер после каждого магазина), но без барьера это все еще магазин как C ++ memory_order_relaxed
. (Или при JIT для x86 asm нагрузки / хранилища на самом деле так же сильны, как и получение / освобождение.)
Кэши являются связными, и буфер хранилища всегда истощает себя (фиксируя кэш L1d) настолько быстро, насколько это возможно, чтобы сделать пространство для выполнения дополнительных магазинов.
Предупреждение: я не знаю много Java, и я точно не знаю, насколько небезопасно / неопределенно назначать не- volatile
в одном потоке и читайте в другом без синхронизации. В зависимости от поведения, которое вы видите, это звучит точно так же, как и в C ++ для того же, что и с переменными, отличными от atomic
(с включенной оптимизацией, как всегда делает HotSpot)
(на основе @ Комментарий Маргарет, я обновил некоторые догадки о том, как я предполагаю, что Java синхронизация работает. Если я что-то неправильно указал, пожалуйста, отредактируйте или прокомментируйте.)
В C ++ гонки данных на не- atomic
переменных всегда являются неопределенным поведением, но, конечно, при компиляции для реальных ISA (которые не работают аппаратно профилактика расы) результаты иногда являются желаемыми.