Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

292
Vistas
Subprocesamiento múltiple de Java: unión de un subproceso pesado de CPU y una palabra clave volátil

Entonces, después de algunas entrevistas de trabajo, quería escribir un pequeño programa para verificar que i++ realmente no sea atómico en Java, y que uno debería, en la práctica, agregar algún bloqueo para protegerlo. Resulta que deberías, pero esta no es la pregunta aquí.

Así que escribí este programa aquí solo para comprobarlo.

El caso es que se cuelga. Parece que el subproceso principal está atascado en la línea t1.join() , aunque ambos subprocesos de trabajo deberían finalizar debido a stop = true de la línea anterior.

Descubrí que el ahorcamiento se detiene si:

  • Agrego algo de impresión dentro de los subprocesos de trabajo (como en los comentarios), probablemente causando que los subprocesos de trabajo en algún momento abandonen la CPU o
  • Si marco la marca boolean stop como volatile , lo que hace que los subprocesos de trabajo vean inmediatamente la escritura, o
  • Si marco el contador t como volatile ... para esto no tengo idea de qué causa el descuelgue.

¿Alguien puede explicar lo que está pasando? ¿Por qué veo el cuelgue y por qué se detiene en esos tres casos?

 public class Test { static /* volatile */ long t = 0; static long[] counters = new long[2]; static /* volatile */ boolean stop = false; static Object o = new Object(); public static void main(String[] args) { Thread t1 = createThread(0); Thread t2 = createThread(1); t1.start(); t2.start(); Thread.sleep(1000); stop = true; t1.join(); t2.join(); System.out.println("counter : " + t + " counters : " + counters[0] + ", " + counters[1] + " (sum : " + (counters[0] + counters[1]) + ")"); } private static Thread createThread(final int i) { Thread thread = new Thread() { public void run() { while (!stop) { // synchronized (o) { t++; // } // if (counters[i] % 1000000 == 0) // { // System.out.println(i + ")" + counters[i]); // } counters[i]++; } }; }; return thread; } }
about 4 years ago · Santiago Trujillo
3 Respuestas
Responde la pregunta

0

Parece que el subproceso principal está atascado en la línea t1.join() , aunque ambos subprocesos de trabajo deberían finalizar debido a stop = true de la línea anterior.

En ausencia de volatile , bloqueo u otro mecanismo de publicación seguro, la JVM no tiene la obligación de hacer que stop = true sea visible para otros subprocesos. Aplicado específicamente a su caso, mientras su subproceso principal duerme durante un segundo, el compilador JIT optimiza su ciclo activo while (!stop) en el equivalente de

 if (!stop) { while (true) { ... } }

Esta optimización particular se conoce como "elevación" de la acción de lectura fuera del ciclo.

Descubrí que el ahorcamiento se detiene si:

  • Agrego algo de impresión dentro de los subprocesos de trabajo (como en los comentarios), lo que probablemente provoque que los subprocesos de trabajo abandonen la CPU en algún momento

No, es porque PrintStream::println es un método sincronizado. Todas las JVM conocidas emitirán una valla de memoria en el nivel de la CPU para garantizar la semántica de una acción de "adquirir" (en este caso, bloquear la adquisición), y esto forzará una recarga de la variable de stop . Esto no es requerido por la especificación, solo una opción de implementación.

  • Si marco la boolean stop de bandera como volátil, lo que hace que los subprocesos de trabajo vean inmediatamente la escritura

La especificación en realidad no tiene requisitos de tiempo de reloj de pared sobre cuándo una escritura volátil debe volverse visible para otros subprocesos, pero en la práctica se entiende que debe volverse visible "muy pronto". Por lo tanto, este cambio es la forma correcta de garantizar que la escritura para stop se publique de manera segura y, posteriormente, sea observada por otros subprocesos que la lean.

  • Si marco el contador t como volátil... para esto no tengo idea de qué causa el descuelgue.

Estos son nuevamente los efectos indirectos de lo que hace la JVM para garantizar la semántica de una lectura volatile , que es otro tipo de acción entre subprocesos de "adquisición".

En resumen, a excepción del cambio que hace que stop sea una variable volátil, su programa pasa de colgarse para siempre a completarse debido a los efectos secundarios accidentales de la implementación de JVM subyacente, que por simplicidad hace más vaciado/invalidación del estado local del subproceso de lo requerido por la especificación.

about 4 years ago · Santiago Trujillo Denunciar

0

Esas pueden ser las posibles razones:

  • Marcar "detener" como volátil evita que su valor se almacene en caché en los estados de subprocesos de trabajo (por ejemplo, registros)
  • Marcar "t" como volátil asegura que se lea el valor actualizado cuando se accede a "t". Sin embargo, este comportamiento también podría obtener el valor actualizado de "stop" si JVM organiza esas variables una cerca de la otra, de modo que puedan leerse y almacenarse en la misma "línea de caché". Intente agregar la anotación @Contenido para ver si el comportamiento persiste también en este caso. Puede encontrar información adicional en: https://en.wikipedia.org/wiki/False_sharing , https://mechanical-sympathy.blogspot.am/2011 /07/false-sharing.html , https://blogs.oracle.com/dave/entry/java_contented_annotation_to_help
  • Llamar a "System.out.println ()" en realidad realiza una llamada al sistema, por lo tanto, hace una transición a la "pila de llamadas nativas", lo que probablemente también hace que la "caché del procesador" se purgue. ¿Qué tiene que hacer una JVM al llamar a un método nativo?

Si está interesado en profundizar en el tema, le sugiero que consulte el libro "Java Concurrency in Practice de Brian Goetz".

about 4 years ago · Santiago Trujillo Denunciar

0

Marcar una variable como volátil es una sugerencia para la JVM, para vaciar/sincronizar los segmentos relacionados de caché entre subprocesos/núcleos cuando se actualiza esa variable. Marcar stop como volátil tiene un mejor comportamiento (pero no perfecto, es posible que tenga algunas ejecuciones adicionales en sus subprocesos antes de que vean la actualización).

Marcar t como volátil me desconcierta por qué funciona, puede ser que debido a que este es un programa tan pequeño, t y stop están en la misma fila en el caché, por lo que cuando uno se descarga/sincroniza, el otro también lo hace.

System.out.println es seguro para subprocesos, por lo que hay cierta sincronización interna. Nuevamente, esto puede estar causando que algunas partes del caché se sincronicen entre los subprocesos.

Si alguien puede agregar a esto, por favor hágalo, también me gustaría escuchar una respuesta más detallada sobre esto.

about 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda