#java #multithreading #thread-safety #mmap #mappedbytebuffer
Вопрос:
Допустим, я сопоставил область памяти [0, 1000], и теперь у меня есть MappedByteBuffer.
Могу ли я читать и записывать в этот буфер из нескольких потоков одновременно без блокировки, предполагая, что каждый поток обращается к другой части буфера для exp. T1 [0, 500), T2 [500, 1000]?
Если вышесказанное верно, можно ли определить, лучше ли создать один большой буфер для нескольких потоков или меньший буфер для каждого потока?
Комментарии:
1. В каком смысле лучше?
Ответ №1:
Подробное вступление:
Если вы хотите научиться самостоятельно отвечать на эти вопросы, проверьте исходные коды их реализации:
- Маппедбюффер: https://github.com/himnay/java7-sourcecode/blob/master/java/nio/MappedByteBuffer.java (обратите внимание, что он все еще абстрактен, поэтому вы не можете создать его напрямую)
- расширяет ByteBuffer: https://github.com/himnay/java7-sourcecode/blob/master/java/nio/ByteBuffer.java
- расширяет буфер: https://github.com/himnay/java7-sourcecode/blob/329bbb33cbe8620aee3cee533eec346b4b56facd/java/nio/Buffer.java (который выполняет только проверку индекса и не предоставляет фактического доступа к какой-либо буферной памяти)
Теперь все становится немного сложнее:
Когда вы захотите выделить MappedByteBuffer, вы получите либо
- HeapByteBuffer: https://github.com/himnay/java7-sourcecode/blob/329bbb33cbe8620aee3cee533eec346b4b56facd/java/nio/HeapByteBuffer.java
- или прямой буферизатор: https://github.com/himnay/java7-sourcecode/blob/329bbb33cbe8620aee3cee533eec346b4b56facd/java/nio/DirectByteBuffer.java
Вместо того, чтобы просматривать интернет-страницы, вы также можете просто загрузить пакеты исходного кода для своей версии Java и прикрепить их в своей среде IDE, чтобы вы могли просматривать код в режимах разработки И отладки. Намного проще.
Короткий (неполный) ответ:
Ни один из них не защищает от многопоточности.
- Поэтому, если вам когда-нибудь понадобится изменить размер MappedByteBuffer, вы можете получить устаревший или даже плохой (исключение ArrayIndexOutOfBoundsException) доступ
- Если размер постоянен, вы можете полагаться на то, что любая реализация будет «потокобезопасной», насколько это касается ваших требований
Кстати, здесь также кроется ошибка реализации в реализации Java:
- MappedByteBuffer расширяет ByteBuffer
- У ByteBuffer есть куча
byte[]
под названием «hb». - DirectByteBuffer расширяет MappedByteBuffer расширяет ByteBuffer
- Таким образом, у DirectByteBuffer все еще есть
byte[] hb
буфер ByteBuffer, -
- но не использует его
-
- и вместо этого создает свой собственный буфер и управляет им
Этот недостаток дизайна связан с пошаговой разработкой этих классов (они не были запланированы и реализованы одновременно) И темой видимости пакетов, что приводит к инверсии зависимости/иерархии реализации.
Теперь к истинному ответу:
Если вы хотите заниматься правильным объектно-ориентированным программированием, вам не следует делиться ресурсами без крайней необходимости. Это ОСОБЕННО означает, что у каждого потока должен быть свой собственный буфер.
Преимущество наличия одного глобального буфера: единственное «преимущество» заключается в уменьшении дополнительного потребления памяти для дополнительных ссылок на объекты. Но это влияние НАСТОЛЬКО МИНИМАЛЬНО (даже не 1:10000 изменений в потреблении оперативной памяти вашего приложения), что вы НИКОГДА этого не заметите. Повсюду так много других объектов, выделенных по множеству странных (Java) причин, что это наименьшая из ваших проблем. Кроме того, вам придется ввести дополнительные данные (границы индекса), что еще больше уменьшает «преимущество».
Большие преимущества наличия отдельных буферов:
- Вам никогда не придется заботиться об арифметике указателя/индекса
-
- особенно когда дело доходит до того, что вам нужно больше потоков в любой момент времени
- Вы можете свободно выделять новые потоки в любое время без необходимости изменять какие-либо данные или выполнять дополнительную арифметику указателей
- вы можете свободно перераспределять/изменять размер каждого отдельного буфера при необходимости (не беспокоясь о требованиях к индексации всех других потоков).
- Отладка: вы можете намного проще находить проблемы, возникающие в результате «записи за пределы границ», потому что, если бы они попытались, плохой поток вышел бы из строя, а не другие потоки, которым пришлось бы иметь дело с поврежденными данными
-
- Java ВСЕГДА проверяет доступ к каждому массиву (например, к обычным массивам кучи
byte[]
), прежде чем обращаться к нему, именно для предотвращения побочных эффектов
- Java ВСЕГДА проверяет доступ к каждому массиву (например, к обычным массивам кучи
-
- вспомните: когда-то в операционных системах был сделан большой шаг к внедрению линейного адресного пространства, чтобы программам НЕ приходилось заботиться о том, где в аппаратной оперативной памяти они загружены.
-
- Ваш дизайн с одним буфером был бы точным шагом назад.
Вывод:
Если вы хотите, чтобы у вас был действительно плохой выбор дизайна, что в дальнейшем значительно усложнит жизнь, вы выбираете один глобальный буфер.
Если вы хотите сделать это надлежащим образом, разделите эти буферы. Никаких запутанных зависимостей и проблем с побочными эффектами.
Комментарии:
1. Исчерпывающий ответ, большое вам спасибо, я в основном задавался вопросом, может ли наличие одного большого буфера повысить производительность из-за какой-то странной внутренней динамики mmap, MappedBytBuffer в Java.
2. Нет. При современных архитектурах процессоров (более 10 лет) каждый процессор/поток имеет свой собственный кэш, поэтому совместное использование одного буфера, вероятно, также может привести к большим недостаткам.