java concurrency memory-mapped-files bytebuffer filechannel

¿Pueden varios subprocesos ver escrituras en un ByteBuffer asignado directamente en Java?



concurrency memory-mapped-files (7)

La asignación de memoria con la JVM es solo una envoltura delgada alrededor de CreateFileMapping (Windows) o mmap (posix). Como tal, tiene acceso directo a la memoria caché del búfer del sistema operativo. Esto significa que estos buffers son lo que el sistema operativo considera que contiene el archivo (y el sistema operativo finalmente sincronizará el archivo para reflejar esto).

Por lo tanto, no es necesario llamar a force () para sincronizar entre procesos. Los procesos ya están sincronizados (a través del sistema operativo, incluso los accesos de lectura / escritura en las mismas páginas). Forzar la sincronización entre el sistema operativo y el controlador de la unidad (puede haber algún retraso entre el controlador de la unidad y los discos físicos, pero no tiene soporte de hardware para hacer nada al respecto).

En cualquier caso, los archivos asignados en memoria son una forma aceptada de memoria compartida entre subprocesos y / o procesos. La única diferencia entre esta memoria compartida y, digamos, un bloque con nombre de memoria virtual en Windows es la eventual sincronización con el disco (de hecho mmap hace la memoria virtual sin un archivo al asignar / dev / null).

La lectura de la memoria de escritura de múltiples procesos / subprocesos todavía necesita cierta sincronización, ya que los procesadores pueden realizar la ejecución fuera de orden (no está seguro de cuánto interactúa con las JVM, pero no puede hacer suposiciones), pero escribir un byte desde un subproceso tendrá las mismas garantías que escribir en cualquier byte en el montón normalmente. Una vez que haya escrito en él, cada subproceso y cada proceso verán la actualización (incluso a través de una operación de lectura / apertura).

Para obtener más información, busque mmap en posix (o CreateFileMapping para Windows, que fue construido casi de la misma manera).

Estoy trabajando en algo que utiliza ByteBuffers creado a partir de archivos asignados en memoria (a través de FileChannel.map() ), así como en ByteBuffers directos en memoria. Estoy tratando de entender las restricciones del modelo de memoria y concurrencia.

He leído todo el Javadoc relevante (y la fuente) para cosas como FileChannel, ByteBuffer, MappedByteBuffer, etc. Parece claro que un ByteBuffer particular (y subclases relevantes) tiene un montón de campos y el estado no está protegido de un modelo de memoria punto de vista. Por lo tanto, debe sincronizar cuando modifique el estado de un ByteBuffer en particular si ese búfer se utiliza en todos los subprocesos. Los trucos comunes incluyen el uso de un ThreadLocal para envolver el ByteBuffer, duplicarlo (mientras está sincronizado) para obtener una nueva instancia que apunte a los mismos bytes asignados, etc.

Ante este escenario:

  1. el administrador tiene un búfer de bytes asignado B_all para todo el archivo (digamos que es <2gb)
  2. el administrador llama a duplicate (), position (), limit () y slice () en B_all para crear un ByteBuffer B_1 más B_1 que una parte del archivo y le da esto al hilo T1
  3. el administrador hace lo mismo para crear un ByteBuffer B_2 apunta a los mismos bytes asignados y le da esto al hilo T2

Mi pregunta es: ¿Puede T1 escribir en B_1 y T2 escribir en B_2 al mismo tiempo y tener la garantía de ver los cambios de cada uno? ¿Podría T3 usar B_all para leer esos bytes y tener la garantía de ver los cambios de T1 y T2?

Soy consciente de que las escrituras en un archivo asignado no se ven necesariamente en todos los procesos a menos que use force () para indicar al sistema operativo que escriba las páginas en el disco. No me importa eso. Supongamos que para esta pregunta, esta JVM es el único proceso que escribe un solo archivo asignado.

Nota: no estoy buscando conjeturas (puedo hacerlas muy bien yo mismo). Me gustaría referencias a algo definitivo sobre lo que (o no) está garantizado para los buffers directos asignados en memoria. O si tiene experiencias reales o casos de prueba negativos, eso también podría servir como evidencia suficiente.

Actualización: he realizado algunas pruebas con varios subprocesos que escriben en el mismo archivo en paralelo y hasta ahora parece que esas escrituras son visibles inmediatamente desde otros subprocesos. No estoy seguro si puedo confiar en eso sin embargo.


Lo más barato que puedes hacer es usar una variable volátil. Después de que un hilo escribe en el área asignada, debe escribir un valor en una variable volátil. Cualquier hilo de lectura debe leer la variable volátil antes de leer el búfer asignado. Al hacer esto se produce un "suceso antes" en el modelo de memoria de Java.

Tenga en cuenta que NO tiene garantía de que haya otro proceso en medio de escribir algo nuevo. Pero si desea garantizar que otros subprocesos puedan ver algo que ha escrito, escribir un texto volátil (seguido de la lectura del subproceso de lectura) funcionará.


No creo que esto esté garantizado. Si el modelo de memoria Java no dice que está garantizado, por definición no está garantizado. Yo guardaría las escrituras del búfer con escrituras sincronizadas o en cola para un hilo que maneja todas las escrituras. Este último juega bien con el almacenamiento en caché de múltiples núcleos (es mejor tener 1 escritor para cada ubicación de RAM).


No, no es diferente de las variables normales de Java o de los elementos de la matriz.


No. El modelo de memoria JVM (JMM) no garantiza que varios subprocesos que mutan (no sincronizados) los datos verán los cambios de otros.

Primero, dado que todos los subprocesos que acceden a la memoria compartida están todos en la misma JVM, el hecho de que se esté accediendo a esta memoria a través de un ByteBuffer asignado es irrelevante (no hay una volatilidad o sincronización implícita en la memoria a la que se accede a través de un ByteBuffer), por lo que la pregunta es equivalente a uno sobre el acceso a una matriz de bytes.

Vamos a reformular la pregunta para que sea sobre matrices de bytes:

  1. Un administrador tiene una matriz de byte[] B_all : byte[] B_all
  2. Se crea una nueva referencia a esa matriz: byte[] B_1 = B_all , y se asigna al subproceso T1
  3. Se crea otra referencia a esa matriz: byte[] B_2 = B_all , y se asigna al hilo T2

¿Las escrituras en B_1 por el hilo T1 se ven en B_2 por el hilo T2 ?

No, no se garantiza que tales escrituras se vean, sin una cierta sincronización explícita entre T_1 y T_2 . El núcleo del problema es que el JIT de la JVM, el procesador y la arquitectura de la memoria son libres de reordenar algunos accesos a la memoria (no solo para molestarlo, sino también para mejorar el rendimiento mediante el almacenamiento en caché). Todas estas capas esperan que el software sea explícito (a través de bloqueos, volátiles u otras sugerencias explícitas) sobre dónde se requiere la sincronización, lo que implica que estas capas son libres de mover cosas cuando no se proporcionan tales sugerencias.

Tenga en cuenta que, en la práctica, ver o no las escrituras depende principalmente del hardware y la alineación de los datos en los distintos niveles de cachés y registros, y de cuán "lejos" están los subprocesos en ejecución en la jerarquía de memoria.

JSR-133 fue un esfuerzo por definir con precisión el modelo de memoria Java alrededor de Java 5.0 (y por lo que sé, todavía es aplicable en 2012). Ahí es donde desea buscar respuestas definitivas (aunque densas): http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf (la sección 2 es la más relevante). Se puede encontrar información más legible en la página web de JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/

Parte de mi respuesta es afirmar que un ByteBuffer no es diferente de un byte[] en términos de sincronización de datos. No puedo encontrar documentación específica que diga esto, pero sugiero que la sección "Seguridad de subprocesos" de la documentación java.nio.Buffer mencionaría algo sobre la sincronización o la volatilidad si fuera aplicable. Dado que el documento no menciona esto, no debemos esperar tal comportamiento.


Supongo que la memoria directa proporciona las mismas garantías o la falta de ellas que la memoria de pila. Si modifica un ByteBuffer que comparte una matriz subyacente o una dirección de memoria directa, un segundo ByteBuffer es otro hilo que puede ver los cambios, pero no se garantiza que lo haga.

Sospecho que incluso si se usa sincronizado o volátil, no está garantizado que funcione, sin embargo, puede hacerlo dependiendo de la plataforma.

Una forma sencilla de cambiar datos entre subprocesos es usar un intercambiador

Basado en el ejemplo,

class FillAndEmpty { final Exchanger<ByteBuffer> exchanger = new Exchanger<ByteBuffer>(); ByteBuffer initialEmptyBuffer = ... a made-up type ByteBuffer initialFullBuffer = ... class FillingLoop implements Runnable { public void run() { ByteBuffer currentBuffer = initialEmptyBuffer; try { while (currentBuffer != null) { addToBuffer(currentBuffer); if (currentBuffer.remaining() == 0) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ... } } } class EmptyingLoop implements Runnable { public void run() { ByteBuffer currentBuffer = initialFullBuffer; try { while (currentBuffer != null) { takeFromBuffer(currentBuffer); if (currentBuffer.remaining() == 0) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ...} } } void start() { new Thread(new FillingLoop()).start(); new Thread(new EmptyingLoop()).start(); } }


Una posible respuesta que he encontrado es usar bloqueos de archivos para obtener acceso exclusivo a la parte del disco asignada por el búfer. Esto se explica con un ejemplo here por ejemplo.

Supongo que esto guardaría realmente la sección del disco para evitar escrituras simultáneas en la misma sección del archivo. Se podría lograr lo mismo (en una única JVM pero invisible para otros procesos) con monitores basados ​​en Java para secciones del archivo de disco. Supongo que sería más rápido con la desventaja de ser invisible para los procesos externos.

Por supuesto, me gustaría evitar el bloqueo de archivos o la sincronización de páginas si jvm / os garantiza la coherencia.