CodeGym/Blog Java/Random-ES/Manejo de hilos. La palabra clave volátil y el método yie...
Autor
Volodymyr Portianko
Java Engineer at Playtika

Manejo de hilos. La palabra clave volátil y el método yield()

Publicado en el grupo Random-ES
¡Hola! Continuamos nuestro estudio de subprocesos múltiples. Hoy conoceremos la volatilepalabra clave y el yield()método. Vamos a sumergirnos :)

La palabra clave volátil

A la hora de crear aplicaciones multiproceso, podemos toparnos con dos serios problemas. En primer lugar, cuando se ejecuta una aplicación de subprocesos múltiples, diferentes subprocesos pueden almacenar en caché los valores de las variables (ya hablamos de esto en la lección titulada 'Uso de volátiles' ). Puede tener la situación en la que un subproceso cambia el valor de una variable, pero un segundo subproceso no ve el cambio, porque está trabajando con su copia en caché de la variable. Naturalmente, las consecuencias pueden ser graves. Supongamos que no se trata de una variable cualquiera, sino del saldo de su cuenta bancaria, que de repente comienza a saltar al azar hacia arriba y hacia abajo :) Eso no suena divertido, ¿verdad? En segundo lugar, en Java, las operaciones para leer y escribir todos los tipos primitivos,longdouble, son atómicos. Bueno, por ejemplo, si cambia el valor de una intvariable en un subproceso y en otro subproceso lee el valor de la variable, obtendrá su valor anterior o el nuevo, es decir, el valor que resultó del cambio. en el hilo 1. No hay 'valores intermedios'. Sin embargo, esto no funciona con longs y doubles. ¿Por qué? Debido al soporte multiplataforma. ¿Recuerdas en los niveles iniciales que dijimos que el principio rector de Java es "escribir una vez, ejecutar en cualquier lugar"? Eso significa soporte multiplataforma. En otras palabras, una aplicación Java se ejecuta en todo tipo de plataformas diferentes. Por ejemplo, en sistemas operativos Windows, diferentes versiones de Linux o MacOS. Se ejecutará sin problemas en todos ellos. Pesando en un 64 bits,longdoubleson las primitivas 'más pesadas' en Java. Y ciertas plataformas de 32 bits simplemente no implementan la lectura y escritura atómica de variables de 64 bits. Estas variables se leen y escriben en dos operaciones. Primero, los primeros 32 bits se escriben en la variable y luego se escriben otros 32 bits. Como resultado, puede surgir un problema. Un subproceso escribe un valor de 64 bits en una Xvariable y lo hace en dos operaciones. Al mismo tiempo, un segundo hilo intenta leer el valor de la variable y lo hace entre esas dos operaciones, cuando se han escrito los primeros 32 bits, pero no los segundos 32 bits. Como resultado, lee un valor intermedio incorrecto y tenemos un error. Por ejemplo, si en una plataforma de este tipo intentamos escribir el número a un 9223372036854775809 a una variable, ocupará 64 bits. En forma binaria, se ve así: 10000000000000000000000000000000000000000000000000000000000000001 El primer hilo comienza a escribir el número en la variable. Primero escribe los primeros 32 bits (10000000000000000000000000000000) y luego los segundos 32 bits (00000000000000000000000000000001) Y el segundo hilo puede quedar atrapado entre estas operaciones, leyendo el valor intermedio de la variable (100000000000000000000000000000000), que son los primeros 32 bits que ya se han escrito. En el sistema decimal, este número es 2.147.483.648. En otras palabras, solo queríamos escribir el número 9223372036854775809 en una variable, pero debido a que esta operación no es atómica en algunas plataformas, tenemos el número maligno 2,147,483,648, que salió de la nada y tendrá un efecto desconocido el programa. El segundo hilo simplemente leyó el valor de la variable antes de que terminara de escribirse, es decir, el hilo vio los primeros 32 bits, pero no los segundos 32 bits. Por supuesto, estos problemas no surgieron ayer. Java los resuelve con una sola palabra clave: volatile. Si usamos elvolatilepalabra clave al declarar alguna variable en nuestro programa…
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…esto significa que:
  1. Siempre se leerá y escribirá atómicamente. Incluso si es de 64 bits doubleo long.
  2. La máquina Java no lo almacenará en caché. Por lo tanto, no tendrá una situación en la que 10 subprocesos estén trabajando con sus propias copias locales.
Así, dos problemas muy serios se resuelven con una sola palabra :)

El método de rendimiento ()

Ya hemos revisado muchos de los Threadmétodos de la clase, pero hay uno importante que será nuevo para usted. Es el yield()método . ¡Y hace exactamente lo que su nombre implica! Manejo de hilos.  La palabra clave volátil y el método yield() - 2Cuando llamamos al yieldmétodo en un subproceso, en realidad se comunica con los otros subprocesos: 'Hola, muchachos. No tengo ninguna prisa particular por ir a ninguna parte, así que si es importante para alguno de ustedes obtener tiempo de procesamiento, tómelo, puedo esperar”. He aquí un ejemplo simple de cómo funciona esto:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
Creamos e iniciamos secuencialmente tres subprocesos: Thread-0, Thread-1y Thread-2. Thread-0comienza primero e inmediatamente cede a los demás. Luego Thread-1se arranca y también cede. Entonces Thread-2se arranca, que también cede. No tenemos más subprocesos, y después Thread-2de ceder su último lugar, el programador de subprocesos dice: 'Hmm, no hay más subprocesos nuevos. ¿A quién tenemos en la cola? ¿ Quién cedió su lugar antes Thread-2? Parece que lo fue Thread-1. Bien, eso significa que lo dejaremos correr'. Thread-1completa su trabajo y luego el planificador de subprocesos continúa su coordinación: 'Está bien, Thread-1terminado. ¿Tenemos a alguien más en la cola?'. Thread-0 está en la cola: cedió su lugar justo antesThread-1. Ahora llega su turno y corre hasta su finalización. Luego, el planificador termina de coordinar los subprocesos: 'Está bien, Thread-2cedió el paso a otros subprocesos y ya están todos terminados. Fuiste el último en ceder, así que ahora es tu turno'. Luego Thread-2corre hasta completarse. La salida de la consola se verá así: Thread-0 cede su lugar a otros Thread-1 cede su lugar a otros Thread-2 cede su lugar a otros Thread-1 ha terminado de ejecutarse. Thread-0 ha terminado de ejecutarse. Thread-2 ha terminado de ejecutarse. Por supuesto, el planificador de subprocesos puede iniciar los subprocesos en un orden diferente (por ejemplo, 2-1-0 en lugar de 0-1-2), pero el principio sigue siendo el mismo.

Sucede antes de las reglas

Lo último que tocaremos hoy es el concepto de ' sucede antes '. Como ya sabe, en Java, el programador de subprocesos realiza la mayor parte del trabajo relacionado con la asignación de tiempo y recursos a los subprocesos para realizar sus tareas. También ha visto repetidamente cómo se ejecutan los subprocesos en un orden aleatorio que, por lo general, es imposible de predecir. Y en general, después de la programación 'secuencial' que hicimos anteriormente, la programación multiproceso parece algo aleatorio. Ya ha llegado a creer que puede usar una gran cantidad de métodos para controlar el flujo de un programa de subprocesos múltiples. Pero los subprocesos múltiples en Java tienen un pilar más: las 4 reglas ' sucede antes '. Comprender estas reglas es bastante simple. Imagine que tenemos dos hilos, AyB. Cada uno de estos subprocesos puede realizar operaciones 1y 2. En cada regla, cuando decimos ' A sucede antes que B ', queremos decir que todos los cambios realizados por el subproceso Aantes de la operación 1y los cambios resultantes de esta operación son visibles para el subproceso Bcuando 2se realiza la operación y posteriormente. Cada regla garantiza que cuando escribe un programa multiproceso, ciertos eventos ocurrirán antes que otros el 100% del tiempo, y que en el momento de la operación, el 2subproceso Bsiempre estará al tanto de los cambios que ese subproceso Arealizó durante la operación 1. Vamos a repasarlos.

Regla 1.

La liberación de un mutex ocurre antes de que otro subproceso adquiera el mismo monitor. Creo que entiendes todo aquí. Si el mutex de un objeto o clase es adquirido por un subproceso, por ejemplo, por subproceso A, otro subproceso (subproceso B) no puede adquirirlo al mismo tiempo. Debe esperar hasta que se libere el mutex.

regla 2

El Thread.start()método ocurre antes Thread.run() . Una vez más, nada difícil aquí. Ya sabe que para comenzar a ejecutar el código dentro del run()método, debe llamar al start()método en el hilo. Específicamente, el método de inicio, ¡no el run()método en sí! Esta regla asegura que los valores de todas las variables establecidas antes Thread.start()de llamar serán visibles dentro del run()método una vez que comience.

Regla 3.

El final del run()método ocurre antes del regreso del join()método. Volvamos a nuestros dos hilos: Ay B. Llamamos al join()método para garantizar que el subproceso Bespere a que se complete Aantes de que haga su trabajo. Esto significa que se garantiza que el método del objeto A run()se ejecutará hasta el final. Y todos los cambios en los datos que ocurren en el run()método de subproceso Aestán cien por ciento garantizados para ser visibles en el subproceso Buna vez que finaliza esperando que el subproceso Atermine su trabajo para que pueda comenzar su propio trabajo.

Regla 4.

Escribir en una volatilevariable ocurre antes de leer de esa misma variable. Cuando usamos la volatilepalabra clave, en realidad siempre obtenemos el valor actual. Incluso con una longo double(hablamos antes sobre los problemas que pueden ocurrir aquí). Como ya comprenderá, los cambios realizados en algunos subprocesos no siempre son visibles para otros subprocesos. Pero, por supuesto, hay situaciones muy frecuentes en las que ese comportamiento no nos conviene. Supongamos que asignamos un valor a una variable en hilo A:
int z;.

z = 555;
Si nuestro Bsubproceso debe mostrar el valor de la zvariable en la consola, fácilmente podría mostrar 0, porque no conoce el valor asignado. Pero la Regla 4 garantiza que si declaramos la zvariable como volatile, los cambios en su valor en un subproceso siempre serán visibles en otro subproceso. Si le sumamos la palabra volatileal código anterior...
volatile int z;.

z = 555;
... luego prevenimos la situación en la que el subproceso Bpodría mostrar 0. La escritura en las volatilevariables ocurre antes de leerlas.
Comentarios
  • Populares
  • Nuevas
  • Antiguas
Debes iniciar sesión para dejar un comentario
Esta página aún no tiene comentarios