Бывает раньше для прямого ByteBuffer
У меня есть прямой ByteBuffer (off-heap) в одном потоке и безопасно публиковать его в другом потоке, используя один из механизмов, предоставленных мне JMM. Связаны ли отношения "до" до родной (вне кучи) памяти, обернутой ByteBuffer? Если нет, то как я могу безопасно публиковать содержимое прямого ByteBuffer из одного потока в другой?
Edit
Это не дубликат Может ли несколько потоков видеть записи в прямом отображенном ByteBuffer в Java?, потому что
- Я не говорю о области mmaped(), но об общей области с кучей
- Я благополучно публикую ByteBuffer
- Я не изменяю одновременно содержимое ByteBuffer, я просто передаю его из одного потока в другой
Изменить 2
Это не дубликат Опции, чтобы сделать потоки Java ByteBuffer безопасными. Я не пытаюсь одновременно модифицировать ByteBuffer из двух разных потоков. Я пытаюсь передать, если из одного потока в другой, и происходит, - до семантики в области внутренней памяти, поддерживаемой прямым ByteBuffer. Первый поток больше не будет изменять или читать из ByteBuffer после его передачи.
Ответы
Ответ 1
Конечно, если вы читаете и пишете ByteBuffer
в Java-коде, используя Java-методы, такие как put
и get
, то происходит связь между вашими изменениями в первом потоке, публикацией/потреблением и, наконец, последующий доступ во втором потоке будет применяться 0 ожидаемым образом. В конце концов, тот факт, что ByteBuffer
поддерживается памятью "off heap", является просто детальностью реализации: он не позволяет методам Java на ByteBuffer
разорвать контракт модели памяти.
Все становится немного туманным, если вы говорите о записи в этот байтовый буфер из собственного кода, который вы вызываете через JNI или другой механизм. Я думаю, что до тех пор, пока вы используете обычные магазины (т.е. Не временные магазины или что-либо, что имеет слабую семантику, чем обычные магазины) в вашем собственном коде, вы будете в порядке. После того, как JMV внутренне реализует магазины для кучи памяти с помощью того же механизма, и в частности методы типа get
и put
будут реализованы с нормальными нагрузками и хранилищами. Публичное действие, которое обычно включает некоторый тип хранилища релизов, будет применяться ко всем предыдущим действиям Java, а также к хранилищам внутри вашего собственного кода.
Вы можете найти экспертную дискуссию в списках рассылки concurrency более или менее этой темы. Точный вопрос: "Могу ли я использовать блокировки Java для защиты буфера, к которому обращается только собственный код", но основные проблемы почти одинаковы. Вывод, похоже, согласуется с вышесказанным: если вы в безопасности, если вы делаете нормальные нагрузки и хранилища в обычную область памяти 1. Если вы хотите использовать более слабые инструкции, вам понадобится забор.
0 Итак, это было немного длинное, замученное предложение, но я хотел прояснить, что существует целая цепочка: перед парами, которые должны быть правильно синхронизированы для этого, чтобы работа: (A) между записью в буфер и хранилищем публикации в первом потоке, (B) хранилище публикации и потребляющая нагрузка (C) потребляющая нагрузка и последующее чтение или запись вторым потоком. Пара (B) является чисто на Java-землях, поэтому следуют регулярным правилам. Вопрос в основном заключается в том, являются ли (A) и (C), которые имеют один "родной" элемент, также отлично.
1 Нормальный в этом контексте более или менее означает тот же тип области памяти, который использует Java, или, по крайней мере, один с такими же надежными гарантиями последовательности в отношении типа используемой Java-памяти. Вы должны уйти от своего пути, чтобы нарушить это, и потому что вы используете ByteBuffer
, вы уже знаете, что область выделена Java и должна играть по обычным правилам (поскольку методы уровня Java на ByteBuffer
нужны для работы в соответствии с моделью памяти, по крайней мере).
Ответ 2
Монитор объектов 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 он идет to 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
.