Бывает-раньше для прямого ByteBuffer
У меня есть прямой ByteBuffer (вне кучи) в одном потоке, и я могу безопасно опубликовать его в другом потоке, используя один из механизмов, предоставленных мне JMM. Распространяется ли отношение "происходит до" на собственную (вне кучи) память, обернутую ByteBuffer? Если нет, то как я могу безопасно опубликовать содержимое прямого ByteBuffer из одного потока в другой?
редактировать
Это не дубликат Может ли несколько потоков видеть записи в ByteBuffer с прямым отображением в Java? так как
- Я имею в виду не область mmaped(), а общую область вне кучи
- Я благополучно опубликовал ByteBuffer
- Я не изменяю одновременно содержимое ByteBuffer, я просто перенес его из одного потока в другой
Редактировать 2
Это не дубликат Опций, чтобы сделать поток Java ByteBuffer безопасным. Я не пытаюсь одновременно модифицировать ByteBuffer из двух разных потоков. Я пытаюсь передать, если из одного потока в другой, и получаю семантику "происходит до" в области собственной памяти, поддерживаемой прямым байтовым буфером. Первый поток больше не будет изменять или читать из ByteBuffer после его передачи.
2 ответа
Конечно, если вы читаете и пишете ByteBuffer
в коде Java, используя методы Java, такие как put
а также get
тогда связь между случаем и событиями между вашими изменениями в первом потоке, публикацией / потреблением и, наконец, последующим доступом во втором потоке будет применять0 ожидаемым образом. Ведь тот факт, что ByteBuffer
поддерживается памятью "off heap" - это просто деталь реализации: она не позволяет ByteBuffer
разорвать модель памяти контракта.
Вещи становятся немного туманными, если вы говорите о записи в этот байтовый буфер из нативного кода, который вы вызываете через JNI или другой механизм. Я думаю, что до тех пор, пока вы используете нормальные хранилища (то есть не временные хранилища или что-либо со слабой семантикой по сравнению с обычными хранилищами) в своем нативном коде, у вас все будет хорошо на практике. В конце концов, JMV внутренне реализует хранилища для кучи памяти через тот же механизм, и, в частности, get
а также put
Методы типа будут реализованы с обычными нагрузками и хранилищами. Действие публикации, которое обычно включает некоторый тип хранилища релизов, будет применяться ко всем предыдущим действиям Java, а также к хранилищам внутри вашего собственного кода.
Вы можете найти некоторое экспертное обсуждение в списках рассылки параллелизма более или менее этой темы. Точный вопрос: "Могу ли я использовать блокировки Java для защиты буфера, доступного только с помощью собственного кода", но основные проблемы практически совпадают. Вывод, похоже, согласуется с вышеизложенным: если вы в безопасности, если вы делаете обычные загрузки и сохраняете в1 область памяти. Если вы хотите использовать более слабые инструкции, вам понадобится забор.
0 Так что это было довольно длинное, замученное предложение, но я хотел прояснить, что существует целая цепочка пар "до того", которые должны быть правильно синхронизированы, чтобы это работало: (A) между записями в буфер и хранилище публикации в первом потоке, (B) хранилище публикации и потребляющая нагрузка (C) потребляющая нагрузка и последующие операции чтения или записи вторым потоком. Пара (B) находится исключительно на Java-земле, поэтому следует обычным правилам. Тогда возникает вопрос, в основном, о том, подходят ли (A) и (C), которые имеют один "нативный" элемент.
1 Нормальный в этом контексте более или менее означает тот же тип области памяти, которую использует Java, или, по крайней мере, область с такими же сильными гарантиями согласованности относительно типа памяти, которую использует Java. Вы должны изо всех сил нарушать это, и потому что вы используете ByteBuffer
Вы уже знаете, что область выделяется Java и должна играть по обычным правилам (так как методы уровня Java на ByteBuffer
нужно работать так, чтобы это соответствовало модели памяти, как минимум).
Семантика порядка " происходит до того" монитора объектов Java описана в §17.4.5 как:
wait
методы классаObject
(§17.2.1) имеют связанные с ними действия блокировки и разблокировки; отношения между ними происходят до того, как эти действия связаны.
Не определено, относится ли это только к объектам, управляемым Java, или к любым данным. В конце концов, Java не заботится о том, что происходит за пределами "мира" Java. Но это также означает, что мы можем экстраполировать спецификацию на любые данные, доступные в мире Java. Тогда отношение к куче становится менее важным. В конце концов, если я синхронизирую потоки, почему это не должно работать для прямого ByteBuffer?
Чтобы подтвердить это, мы можем взглянуть на то, как это на самом деле реализовано в OpenJDK.
Если мы посмотрим внимательно, мы увидим, что ObjectMonitor::wait
кроме всего прочего:
OrderAccess::fence();
А также ObjectMonitor::exit
(бизнес конец notify
/ notifyAll
) делает:
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::storeload() ;
И то и другое fence()
а также storeload()
результат в глобальном заборе памяти StoreLoad:
inline void OrderAccess::storeload() { fence(); }
На SPARC он генерирует membar
инструкция:
__asm__ volatile ("membar #StoreLoad" : : :);
И на x86 это идет к membar(Assembler::StoreLoad)
и впоследствии:
// Serializes memory and blows flags
void membar(Membar_mask_bits order_constraint) {
if (os::is_MP()) {
// We only have to handle StoreLoad
if (order_constraint & StoreLoad) {
// All usable chips support "locked" instructions which suffice
// as barriers, and are much faster than the alternative of
// using cpuid instruction. We use here a locked add [esp],0.
// This is conveniently otherwise a no-op except for blowing
// flags.
// Any change to this code may need to revisit other places in
// the code where this idiom is used, in particular the
// orderAccess code.
lock();
addl(Address(rsp, 0), 0);// Assert the lock# signal here
}
}
}
Итак, у вас это есть, это просто барьер памяти на уровне процессора. Подсчет ссылок и сбор мусора вступают в игру на гораздо более высоком уровне.
Это означает, что, по крайней мере, в OpenJDK, любая запись в память, выданная ранее Object.notify
будет упорядочен перед любым чтением, выпущенным после Object.wait
,