sun.misc.Unsafe.putOrdered должен делать то, что вы хотите - хранилище с блокировкой, подразумеваемой на x86 с помощью volatile. Я полагаю, что компилятор не будет перемещать инструкции вокруг него.
Это то же самое, что и lazySet для AtomicInteger и его друзей, но его нельзя использовать напрямую с ByteBuffer.
В отличие от классов volatile
или AtomicThings
, этот метод применяется к конкретным операциям записи, в которых вы его используете, а не к определению члена, поэтому его использование не подразумевает ничего для чтения.
Похоже, вы пытаетесь реализовать что-то вроде seqlock - это означает, что вам нужно избегать повторного заказа между чтениями счетчика версий A
и чтениями / записью самих данных. Обычное int не сократит его - поскольку JIT может делать всевозможные непослушные вещи. Я бы порекомендовал использовать volatile int для вашего счетчика, но затем записать его с помощью putOrdered
. Таким образом, вы не платите цену за изменчивую запись (обычно дюжину циклов или больше), в то время как получаете барьер компилятора, подразумеваемый изменчивым чтением (а аппаратный барьер для этих чтений не работает, что делает их быстрыми. ).
С учетом всего сказанного, я думаю, что вы находитесь здесь в серой зоне, потому что lazySet
не является частью формальной модели памяти и не полностью вписывается в рассуждения о том, что происходит до, поэтому вам нужно более глубокое понимание фактического JIT. и аппаратная реализация, чтобы увидеть, можно ли объединить вещи таким образом.
Наконец, даже с изменчивым чтением и записью (игнорируя lazySet
), я не думаю, что ваш seqlock является правильным с точки зрения модели памяти Java, потому что изменчивые записи устанавливают только то, что происходит раньше между этой записью и последующим чтением на другом поток и более ранние действия в потоке записи, но не между чтением и действиями после записи в потоке записи. Иными словами, это однонаправленный забор, а не двунаправленный. Я считаю, что записи в версии N + 1 в ваш общий регион могут быть видны потоку чтения, даже если он дважды читает A == N.
Пояснение из комментария:
Volatile только создает односторонний барьер. Это очень похоже на семантику получения / выпуска, используемую WinTel в некоторых API. Например, предположим, что A, Bv и C изначально равны нулю:
Thread 1:
A = 1; // 1
Bv = 1; // 2
C = 1; // 3
Thread 2:
int c = C; // 4
int b = Bv; // 5
int a = A; // 6
Здесь летучим только Bv. Два потока делают что-то похожее по концепции на ваши писатели и читатели seqlock - поток 1 записывает некоторые данные в одном порядке, а поток 2 читает те же данные в обратном порядке и пытается рассуждать о порядке их выполнения.
Если поток два имеет b == 1, тогда a == 1 всегда, потому что 1 происходит до 2 (порядок программы), а 5 происходит до 6 (порядок программы), и наиболее критично 2 происходит до 5, поскольку 5 читает записанное значение at 2. Таким образом, запись и чтение Bv действует как забор. Вещи выше (2) не могут «двигаться ниже» (2), а элементы ниже (5) не могут «двигаться вверх» 5. Обратите внимание, что я ограничил движение только в одном направлении непосредственно для каждого потока, но не в обоих, что подводит нас к следующему пример:
Эквивалентно вышесказанному, вы можете предположить, что если a == 0, то также c == 0, поскольку C пишется после a и читается до него. Однако летучие вещества не гарантируют этого. В частности, рассуждения, приведенные выше, не препятствуют перемещению (3) выше (2), как это наблюдается в потоке 2, и не препятствуют перемещению (4) ниже (5).
Обновление:
Посмотрим конкретно на ваш пример.
Я считаю, что это может произойти, если развернуть цикл записи, который происходит в p1.
p1:
i = 0
A = 0
// (p1-1) write data1 to B
A = ++i; // (p1-2) 1 assigned to A
A=0 // (p1-3)
// (p1-4) write data2 to B
A = ++i; // (p1-5) 2 assigned to A
p2:
a1 = A // (p2-1)
//Read from B // (p2-2)
a2 = A // (p2-3)
if a1 == a2 and a1 != 0:
Скажем, p2 видит 1 для a1 и a2. Это означает, что между p2-1 и p1-2 (и расширением p1-1), а также между p2-3 и p1-2 произошла ошибка. Однако между чем-либо в p2 и p1-4 происходит «раньше». Фактически, я считаю, что при чтении B на p2-2 можно наблюдать второе (возможно, частично завершенное) чтение на p1-4, которое может «перемещаться выше» изменчивой записи на p1-2 и p1-3.
Достаточно интересно, что я думаю, вы могли бы задать новый вопрос только по этому поводу - забудьте о более быстрых барьерах - работает ли это вообще даже с volatile?
person
BeeOnRope
schedule
02.02.2013