vulnerability sistemas seccion race operativos ejemplo critica condiciones condicion concurrencia competencia carrera java multithreading jvm race-condition

java - sistemas - ¿Cómo maneja internamente la JVM las condiciones de carrera?



race condition vulnerability (5)

Si varios subprocesos intentan actualizar la misma variable miembro, se denomina condición de carrera. ¿Pero me interesaba más saber cómo la JVM lo maneja internamente si no lo manejamos en nuestro código haciéndolo sincronizado o algo más? ¿Colgará mi programa? ¿Cómo reaccionará la JVM? Pensé que la JVM crearía temporalmente un bloque de sincronización para esta situación, pero no estoy seguro de qué sucedería exactamente.

Si alguno de ustedes tiene alguna idea, sería bueno saberlo.


El término preciso es una carrera de datos , que es una especialización del concepto general de una condición de carrera . El término carrera de datos es un concepto oficial, precisamente especificado, lo que significa que surge de un análisis formal del código.

La única forma de obtener la imagen real es ir y estudiar el capítulo del Modelo de memoria de la especificación del lenguaje Java, pero esta es una vista simplificada: siempre que tenga una carrera de datos, casi no hay garantía en cuanto al resultado y la secuencia de lectura. puede ver cualquier valor que se haya escrito en la variable. Ahí también se encuentra la única garantía: el hilo no observará un valor "fuera del aire", como nunca se escribió. Bueno, a menos que estés tratando con long o double , entonces puedes ver escrituras desgarradas.


En resumen, la JVM asume que el código está libre de carreras de datos al traducirlo en código de máquina. Es decir, si el código no está sincronizado correctamente, la especificación del lenguaje Java solo proporciona garantías limitadas sobre el comportamiento de ese código.

La mayoría del hardware moderno también asume que el código está libre de carreras de datos al ejecutarlo. Es decir, si el código no está correctamente sincronizado, el hardware solo otorga garantías limitadas sobre el resultado de su ejecución.

En particular, la especificación del lenguaje Java guarantees lo siguiente solo en ausencia de una carrera de datos:

  • visibilidad : la lectura de un campo produce el último valor que se le asignó (no está claro qué escritura fue la última , y las escrituras de variables largas o dobles guarantees )
  • orden : si una escritura es visible, también lo son las escrituras que la preceden. Por ejemplo, si un hilo se ejecuta:

    public class IntCounter { private int i; public IntCounter(int i){ this.i = i; } public void incrementInt(){ i++; } public int getInt(){ return i; } }

    otro hilo puede leer x solo después de que el constructor de FancyObject haya ejecutado completamente.

En presencia de una carrera de datos, estas garantías son nulas e inválidas. Es posible que un hilo de lectura nunca vea una escritura. También es posible ver la escritura de x , sin ver el efecto del constructor que lógicamente precedió a la escritura de x . Es muy poco probable que el programa sea correcto si no se pueden hacer tales suposiciones básicas.

Sin embargo, una carrera de datos no comprometerá la integridad de la Máquina Virtual de Java. En particular, la JVM no se bloqueará ni se detendrá, y seguirá garantizando la seguridad de la memoria (es decir, evitará la corrupción de la memoria) y cierta semántica de los campos finales .


Java proporciona synchronized y volatile para hacer frente a estas situaciones. Usarlos correctamente puede ser frustrantemente difícil, pero tenga en cuenta que Java solo está exponiendo la complejidad de las arquitecturas modernas de CPU y memoria. Las alternativas serían siempre errar por el lado de la precaución, sincronizando efectivamente todo lo que mataría el rendimiento; o ignore el problema y no ofrezca seguridad de subprocesos en absoluto. Y, afortunadamente, Java proporciona excelentes construcciones de alto nivel en el paquete java.util.concurrent , por lo que a menudo se puede evitar tratar con cosas de bajo nivel.


La JVM manejará bien la situación (es decir, no se bloqueará ni se quejará), ¡pero es posible que no obtenga un resultado que le guste!

Cuando se involucran múltiples hilos, Java se vuelve diabólicamente complicado e incluso un código que parece obviamente correcto puede resultar horriblemente roto. Como ejemplo:

x = new FancyObject();

es defectuoso de muchas maneras.

Primero, digamos que i es actualmente 0 y el hilo A y el hilo B llaman a incrementInt() casi al mismo tiempo. Existe el peligro de que ambos vean que i es 0, luego incrementan 1 y luego guardan el resultado. Así que al final de las dos llamadas, ¡solo es 1, no 2!

Ese es el problema de la condición de carrera con el código, pero hay otros problemas relacionados con la visibilidad de la memoria. Cuando el hilo A cambia una variable compartida, no hay garantía (sin sincronización) de que el hilo B nunca verá los cambios.

Entonces, el hilo A podría incrementar i 100 veces, y una hora más tarde, el hilo B, que llama a getInt (), podría ver i como 0, o 100 o en cualquier lugar intermedio

Lo único que puedes hacer si estás ahondando en la concurrencia de Java es leer Concurrencia de Java en la práctica por Brian Goetz et al. (De acuerdo, probablemente haya otras buenas maneras de aprenderlo, pero este es un gran libro escrito por Joshua Bloch, Doug Lea y otros)


Tal vez me esté perdiendo algo, pero ¿qué hay que manejar? Todavía hay un hilo que llegará primero. Dependiendo del hilo que sea, ese hilo solo actualizará / leerá alguna variable y pasará a la siguiente instrucción. No puede construir mágicamente un bloque de sincronización, realmente no sabe lo que quieres hacer. Entonces, en otras palabras, lo que suceda dependerá del resultado de la "raza".

Tenga en cuenta que no me gusta mucho el nivel inferior, así que quizás no entiendo completamente la profundidad de su pregunta.