3

Uso de hilos en Java(parte 4)

Anteriormente hemos visto:

  • Ejecución secuencial.
  • Uso de concurrencia heredando de la clase Thread e implementar la interfaz Runnable.
  • Particionamiento del trabajo entre los hilos.
  • Comparación del tiempo de la ejecución secuencial vs la ejecución paralela.
  • Ejecutores : Explicación, tipos, ventajas y desventajas.
  • Ejemplo de uso de ejecutores.
    Ahora veremos:
  • Entrelazado de instrucciones, sus consecuencias y código de ejemplo de entrelazado.
  • Solución e implementación de la solución al problema.

Java como el resto de lenguajes de programación de alto nivel emplea un código que luego será traducido a código ensamblador para que nuestras máquinas puedan ejecutar nuestros programas, en la fase de traducción se desglosan las instrucciones que tiene nuestro código, os pongo un ejemplo, el siguiente código Java:

x = x + 1;

Se traduciría a ensamblador obteniendo un código ensamblador que realizaría estas acciones:

1. Cargar el valor de X en un registro que actuará como factor izquierdo dela suma. LOAD(X) => En el registro %eax.
2. Cargar el valor constante 1 en un registro que actuará como factor derecho dela suma. LOAD(1) => En el registro %ebx.
3. Realizar la suma entre ambos(el resultado se guarda en el segundo registro). ADD(%ebx, %eax) => Guarda la suma en %eax.
4. Guardar el valor obtenido dela suma enla secciónde memoria enlaque reside el valor de X. STORE(%eax, X) => Guarda el valor dela suma enla variable X

El código no es exactamente así pero lógicamente es así como nuestras máquinas pueden ejecutar sumas en Java, C++, Python, etc… Todo gracias a esta traducción.
Ahora bien, tomemos el siguiente bucle for para realizar modificaciones en la variable X:

for(int i = 0; i < 100; i++){
x = x + 1;
}

Este código funciona correctamente si solo es ejecutado por un hilo(el principal en el caso de la ejecución de nuestros programas), sin embargo, ¿que ocurre si hay más hilos realizando varios bucles for como ese y alterando el valor de la misma variable X de forma simultanea? Veamos el código de la situación:

public class Hilos extends Thread {
  static int X_Compartida = 0;

  public void run() {
    for (int i = 0; i < 2000; i++) {
      X_Compartida = X_Compartida + 1;
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Hilos[] hilos = new Hilos[10];for (int i = 0; i < 10; i++) {
      hilos[i] = new Hilos(); // Creamos los hilos.
    }
    for (int i = 0; i < 10; i++) {
      hilos[i].start(); // Lanzamos la ejecucion.
    }
    for (int i = 0; i < 10; i++) {
      hilos[i].join(); // El hilo principal esperará a los 10 hilos creados.
    }
    System.out.println("El resultado deberia ser 10 * 2000 = 20000 sin embargo X vale " + X_Compartida + " ===> ENTRELAZADO");
  }
}

Si ejecutamos el código obtenemos:
entrelazado.png
Para continuar la explicación piensa en las 4 líneas que código ensamblador generadas por la suma X = X + 1, al usar 3 hilos podriamos obtener una ejecución como esta:
entrelazadotabla.png
Soy consciente de la posibilidad que te este resultando difícil entender esto, no te preocupes, es un concepto difícil debido a que es necesario entender como funcionan los lenguajes por dentro y cómo trabajan varios hilos a la vez, pero si consigues entender esto, habrás conseguido entender uno de los conceptos que te permitirán manejar la concurrencia, continuemos.
Es hora de hablar de la solución a este problema, nuestra solución consistirá en implementar una zona que evite esta situación, existen muchas soluciones y en estos tutoriales vamos a ver varias de ellas, la primera forma que os mostraré será usando synchronized, Java provee una palabra reservada que define un código que será seguro ante el acceso de varios hilos a la vez, esta palabra es synchronized y se puede utilizar de dos formas:

  • Crear una sección synchronized.
  • Utilizar un método estático synchronized.
    Veamos un ejemplo de ambas, para la primera forma:
public class Ejemplo extends Thread {
  static int X_Compartida = 0;
  static Object cerrojo = new Object();

  public void run() {
    // Primera forma: Usando un bloque synchronized, necesitamos un objeto que actue// como cerrojo => cerrar cuando entre un hilo y abrir cuando el hilo termine.for (int i = 0; i < 1000; i++) {
      synchronized (cerrojo) {
        X_Compartida++;
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    int N = 10;
    Ejemplo hilos[] = new Ejemplo[N];for (int i = 0; i < N; i++) {
      hilos[i] = new Ejemplo();
    }
    for (int i = 0; i < N; i++) {
      hilos[i].start();
    }
    for (int i = 0; i < N; i++) {
      hilos[i].join();
    }
    System.out.println("El valor final de X es " + X_Compartida + " y deberia ser 10000 ====> Evitamos la inconsistencia del resultado!");
  }
}

Para la segunda forma cambiamos el método run() y agregamos el nuevo método:

publicvoidrun(){
    sincronizado();
  }

  // Segunda forma: Llamando a un método synchronized.publicstaticsynchronizedvoidsincronizado(){
    for (int i = 0; i < 1000; i++) {
      X_Compartida++;
    }
  }

Obtenemos el siguiente resultado:
resultado.png

En los próximos tutoriales te explicaré como funciona esto interiormente además de otras alternativas, puedes enviarme un mensaje comentándome tus dudas y te trataré de ayudar, muchas gracias por ver el contenido!

Escribe tu comentario
+ 2