Бывает раньше для прямого 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.