Происходит раньше для прямого ByteBuffer

У меня есть прямой ByteBuffer (вне кучи) в одном потоке, и я безопасно публикую его в другом потоке, используя один из механизмов, предоставленных мне JMM. Распространяется ли отношение «происходит до» на собственную (вне кучи) память, обернутую ByteBuffer? Если нет, то как я могу безопасно опубликовать содержимое прямого ByteBuffer из одного потока в другой?

Изменить

Это не дубликат может быть несколько потоки видят записи в ByteBuffer с прямым отображением в Java? потому что

  • Я говорю не о регионе mmaped(), а об общей области вне кучи.
  • Я безопасно публикую ByteBuffer
  • Я не одновременно изменяю содержимое ByteBuffer, я просто переношу его из одного потока в другой.

Изменить 2

Это не дубликат параметров для обеспечения безопасности потоков Java ByteBuffer. я не пытаюсь одновременно изменить ByteBuffer из двух разных потоков. Я пытаюсь передать if из одного потока в другой и получить семантику «происходит до» в области собственной памяти, поддерживаемой прямым ByteBuffer. Первый поток больше не будет изменять или читать из ByteBuffer после его передачи.


person Philippe Marschall    schedule 01.11.2017    source источник
comment
Я не говорю об области, обработанной mmap(), и я безопасно публикую ByteBuffer.   -  person Philippe Marschall    schedule 01.11.2017
comment
Если вы синхронизируете потоки на мониторе, это действует как ограждение памяти, т.е. все записи до синхронизации будут видны в другом потоке после синхронизации.   -  person rustyx    schedule 01.11.2017
comment
@RustyX, вы уверены, что это относится и к регионам вне кучи?   -  person Philippe Marschall    schedule 01.11.2017
comment
Этот ответ цитирует Buffer javadoc, в котором говорится, что буферы небезопасны для использования несколькими параллельными потоками. Если буфер должен использоваться более чем одним потоком, доступ к буферу должен контролироваться соответствующей синхронизацией.   -  person SpaceTrucker    schedule 01.11.2017
comment
@RustyX Я тоже не уверен, поэтому ищу авторитетный ответ. Откуда вы берете уверенность в том, что семантика захвата/освобождения мьютекса должна применяться к области собственной памяти, поддерживаемой прямым байтовым буфером?   -  person Philippe Marschall    schedule 01.11.2017
comment
@SpaceTrucker, это не мой вариант использования, я не одновременно изменяю буфер, я перебрасываю его из одного потока в другой. Первый поток больше не будет изменять его или читать из него. Я не ищу способ одновременно изменить ByteBuffer. Я ищу способ получить семантику отношений «происходит до» в области собственной памяти, поддерживаемой прямым ByteBuffer.   -  person Philippe Marschall    schedule 01.11.2017
comment
@PhilippeMarschall Тогда, возможно, вам лучше просто передать производителя буфера потоку, который читает из буфера, чем уже созданный буфер.   -  person SpaceTrucker    schedule 01.11.2017
comment
Этот вопрос следует открыть повторно, потому что он существенно отличается от связанных дубликатов, как указано в редакциях ОП.   -  person SpaceTrucker    schedule 01.11.2017
comment
@PhilippeMarschall - вы должны уточнить, как вы пишете в байтовый буфер: это через Java-код или у вас есть собственный код, который вы вызываете?   -  person BeeOnRope    schedule 02.11.2017


Ответы (2)


Конечно, если вы читаете и пишете ByteBuffer в коде Java, используя методы Java, такие как put и get, тогда будет применяться отношение "до" между вашими изменениями в первом потоке, публикацией/потреблением и, наконец, последующим доступом во втором потоке< sup>0 ожидаемым образом. В конце концов, тот факт, что ByteBuffer поддерживается памятью "вне кучи", является просто деталью реализации: он не позволяет методам Java в ByteBuffer нарушать контракт модели памяти.

Все становится немного туманным, если вы говорите о записи в этот байтовый буфер из собственного кода, который вы вызываете через JNI или другой механизм. Я думаю, что пока вы используете обычные хранилища (т. е. не временные хранилища или что-то, что имеет слабую семантику, чем обычные хранилища) в вашем собственном коде, на практике все будет в порядке. В конце концов, JMV внутренне реализует сохранение в куче памяти с помощью одного и того же механизма, и, в частности, методы типа get и put будут реализованы с обычными загрузками и сохранениями. Действие публикации, которое обычно включает в себя некоторый тип хранилища выпуска, будет применяться ко всем предыдущим действиям Java, а также к хранилищам внутри вашего собственного кода.

Вы можете найти некоторые -td10427.html" rel="nofollow noreferrer">экспертное обсуждение в списках рассылки параллелизма более или менее по этой теме. Точный вопрос заключается в следующем: «Могу ли я использовать блокировки Java для защиты буфера, доступ к которому осуществляется только собственным кодом», но основные проблемы в значительной степени такие же. Вывод кажется согласующимся с вышеизложенным: если вы в безопасности, если вы выполняете нормальную загрузку и сохраняете в обычную область памяти1. Если вы хотите использовать более слабые инструкции, вам понадобится забор.


0 Это было немного длинное, вымученное предложение, но я хотел прояснить, что существует целая цепочка событий-до пар, которые должны быть правильно синхронизированы, чтобы это работало: ( A) между записью в буфер и хранилищем публикации в первом потоке, (B) хранилищем публикации и потребляющей нагрузкой (C) потребляющей загрузкой и последующими операциями чтения или записи вторым потоком. Пара (B) находится исключительно в Java-стране, поэтому следует обычным правилам. Тогда вопрос в основном заключается в том, подходят ли (A) и (C), которые имеют один «родной» элемент, также хорошо.

1 Нормальный в этом контексте более или менее означает тот же тип области памяти, что и Java, или, по крайней мере, один с строгими гарантиями согласованности в отношении типа памяти. Ява использует. Вы должны приложить все усилия, чтобы нарушить это, и, поскольку вы используете ByteBuffer, вы уже знаете, что область выделена Java и должна играть по обычным правилам (поскольку методы уровня Java на ByteBuffer должны работать в как минимум в соответствии с моделью памяти).

person BeeOnRope    schedule 01.11.2017

Семантика порядка происходит-до монитора объектов 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.

person rustyx    schedule 01.11.2017
comment
как бы мне это ни нравилось - эти являются деталями реализации, на которые я бы не стал полагаться. Единственное, на что можно положиться, — это JLS. Например, правило о final полях гласит, что каждое поле должно быть окончательным в JLS для безопасной публикации конструктора; но фактическая реализация заботится только о хотя бы одном - person Eugene; 02.11.2017