2009-12-14 10 views
15

Como programador de C++ cada vez estoy más familiarizado con Java, me resulta un poco extraño ver compatibilidad a nivel de lenguaje para bloquear objetos arbitrarios sin ningún tipo de declaración de que el objeto admite dicho bloqueo. Crear mutexes para cada objeto parece ser un alto costo para ser elegido automáticamente. Además del uso de memoria, los mutex son recursos limitados del sistema operativo en algunas plataformas. Podrías girar el bloqueo si los mutex no están disponibles, pero las características de rendimiento son significativamente diferentes, lo que podría afectar la predictibilidad.¿Crea la JVM un mutex para cada objeto para implementar la palabra clave 'sincronizada'? Si no, ¿cómo?

¿Es la JVM lo suficientemente inteligente en todos los casos para reconocer que un objeto en particular nunca será el objetivo de la palabra clave sincronizada y así evitar crear el mutex? Los mutex se pueden crear de forma perezosa, pero eso plantea un problema de arranque que requiere un mutex, e incluso si eso se soluciona, supongo que todavía habrá un sobrecarga para rastrear si un mutex ya se ha creado o no. Así que supongo que si tal optimización es posible, debe hacerse en tiempo de compilación o inicio. En C++, tal optimización no sería posible debido al modelo de compilación (no se podía saber si el bloqueo de un objeto se usaría a través de los límites de la biblioteca), pero no sé lo suficiente acerca de la compilación y el enlace de Java para saber si se aplican las mismas limitaciones

+0

Estoy bastante seguro de que 'synchronized' se maneja internamente en la JVM, en lugar de depender de los recursos del nivel del sistema operativo. –

+0

Eso no puede ser completamente cierto. Estoy de acuerdo en que se puede implementar de cualquier forma que la JVM desee, pero la JVM necesariamente debe confiar en la comunicación a nivel de sistema operativo en algún lugar de forma interna. Puede escribir su propia clase mutex que gira bloqueos, pero si desea que el programador del sistema operativo ponga hilos para dormir y los despierte adecuadamente, en algún momento debe informar al sistema operativo acerca de su exclusión mutua, y ese tipo de registros son los limitados recurso. –

+0

No es necesario que informe al sistema operativo acerca de su mutex; solo tiene que indicarlo cuando es necesario activar ciertos subprocesos. –

Respuesta

15

Hablando como alguien que ha estudiado la forma en que algunas JVM implementan las cerraduras ...

El enfoque normal es comenzar con un par de bits reservados en la palabra del encabezado del objeto. Si el objeto nunca está bloqueado, o si está bloqueado pero no hay contención, se mantiene de esa manera. Si y cuando se produce una contención en un objeto bloqueado, la JVM infla la cerradura en una estructura de datos mutex completa, y permanece así durante toda la vida del objeto.

EDIT - Me acabo de dar cuenta de que el OP estaba hablando de mutexes compatibles con el sistema operativo. En los ejemplos que he analizado, los mutex desinflados se implementaron directamente usando instrucciones CAS y similares, en lugar de usar funciones de biblioteca pthread, etc.

+0

Al inflar, ¿quiere decir que algo está sucediendo como la optimización de cadenas pequeñas en C/C++? (http://tulrich.com/rants-2009.html#d2009-01-03T00:00:00Z) Básicamente, la información comienza como bits empaquetados dentro de una palabra, pero cuando se cumplen ciertas condiciones esa palabra comienza a interpretarse como un puntero a un objeto con más campos? –

+0

Conceptualmente, sí. –

+1

Usando solo instrucciones CAS, ¿cómo se obtiene el programador del sistema operativo para poner un hilo en modo de suspensión? –

2

Nunca puede estar seguro de que un objeto nunca se utilizará como un candado (considere la reflexión). Normalmente, cada objeto tiene un encabezado con algunos bits dedicados al bloqueo. Es posible implementarlo de manera que el encabezado solo se agregue según sea necesario, pero eso se complica un poco y es probable que necesite un encabezado de todos modos (clase (equivalente a "vtbl" y tamaño de asignación en C++), código hash y recolección de basura) .

Here's a wiki page on the implementation of synchronisation in the OpenJDK.

(En mi opinión, la adición de un bloqueo para cada objeto fue un error.)

+0

¿Hay algún lugar donde pueda leer más sobre este 'encabezado'? Como dices que probablemente lo necesites de todos modos, supongo que se usa para cosas que no sean de bloqueo. –

+0

¡Todo está en la fuente GPL/JRL/JIUL! –

+0

Es cierto, pero esperaba que si los programadores de Java en general están interesados ​​en las respuestas a estas preguntas, supongo que alguien ha escrito algo más fácilmente digerible que millones de líneas de código fuente. Si me intereso lo suficiente y tengo suficiente tiempo, podría hacerlo eventualmente;) –

2

Esto es realmente un detalle de implementación de la JVM, y diferentes JVM pueden implementarlo de manera diferente. Sin embargo, definitivamente es no algo que se puede optimizar en tiempo de compilación, ya que los enlaces de Java en tiempo de ejecución, y esto es posible para un código desconocido para obtener un objeto creado en código anterior y comenzar a sincronizar en él.

Tenga en cuenta que en Java lingo, la primitiva de sincronización se denomina "monitor" en lugar de mutex, y es compatible con operaciones especiales de bytecode. Hay una explicación bastante detallada here.

+0

Buen enlace, gracias. –

1

no puede JVM utilizar instrucciones de comparación y cambio directamente?digamos que cada objeto tiene un campo lockingThreadId almacenar el identificador del hilo que está bloqueando él,

while(compare_and_swap (obj.lockingThreadId, null, thisThreadId) != thisTheadId) 
    // failed, someone else got it 
    mark this thread as waiting on obj. 
    shelf this thead 

//out of loop. now this thread locked the object 

do the work 

obj.lockingThreadId = null; 
wake up threads waiting on the obj 

este es un modelo de juguete, pero no parece demasiado caro, y no realiza ningún basan en el sistema operativo.

+1

P.S. la opción de diseño de permitir que se bloquee cualquier objeto probablemente sea incorrecta. tal vez solo se permita cierto tipo de objetos para fines de sincronización, al igual que solo se puede lanzar cierto tipo de objetos. 'synchronized (a_normal_object) {}' es muy confuso para los nuevos alumnos, ellos pensaban que el objeto está protegido y ningún otro hilo puede escribir en él. si tienen que usar un objeto de bloqueo diferente para proteger su objeto normal, sería más fácil entender lo que está sucediendo. – irreputable

+1

La JVM tiene que comunicarse con el sistema operativo en algún momento para que el subproceso quede en reposo y pueda reactivarlo nuevamente, porque el planificador del sistema operativo es el responsable de ello. Si la JVM lo usa directamente, todas las esperas serían bloqueos de giro, que son ineficaces para largas esperas. Una implementación real probablemente gira un poco y luego se va a dormir en un semáforo real del sistema operativo. –

+0

"Si la JVM lo usa directamente" <- por "eso" me refiero a la instrucción CAS –

Cuestiones relacionadas