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

Другие вопросы по тегам