Cuando uno aprende java el tema de hacer una aplicación multi-hilos es uno de los mas divertidos y sanamente complejos que existen, ya que si no lo haces en orden podrías terminar en un verdadero infierno de código spaguetti.

Hay, a mi criterio, 3 formas de crear un thread:

  1. Haciendo una clase que haga la tarea deseada, que implemente la interfaz Runnable
  2. Haciendo una clase que haga la tarea deseada que herede la clase Thread
  3. Pasarle un Runnable al constructor de Thread creando una clase anónima

Una de estas 3 formas es poco recomendable, pero lo diré a su tiempo, mientras tanto… manos al código!

Runnable

Para este ejemplo vamos a definir 3 clases:

  • DuckFactory: produce patitos en tiempo real
  • PigFactory: produce cerditos en tiempo real
  • Outlet: es la clase que corre 2 threads en paralelo

La clase DuckFactory se veria asi:

package com.ricardogeek.runnables;

import java.util.Random;

public class DuckFactory implements Runnable {
    
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.println("Pato " + i);

            try {
                Thread.sleep(numeroRandom(1000, 5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static int numeroRandom(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }

}

Y similarmente la clase PigFactory se veria asi:

package com.ricardogeek.runnables;

import java.util.Random;

public class PigFactory implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Cerdo " + i);
            try {
                Thread.sleep(numeroRandom(1000, 5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static int numeroRandom(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }

}

Noten que de la interfaz Runnable es necesario implementar el método run()  y en el poner la operación que estará llevando a cabo el Thread, en este caso imprimir patos o cerdos, y luego un pequeño retraso aleatorio entre cada producción. El método privado numeroRandom es unicamente para generar el intervalo de retraso aleatorio entre cada iteración del ciclo.

Lo que está dentro del método run() se ejecuta cuando se llama al método start() que es propio a la clase Thread, ya veremos en nuestra clase Outlet como invocarlos y ejecutarlos.

La clase Outlet, seria algo asi:

package com.ricardogeek.runnables;

public class Outlet {
    private static final DuckFactory duckFactory = new DuckFactory();
    private static final PigFactory pigFactory = new PigFactory();

    public static void main(String[] args) {
        Thread duckThread = new Thread(duckFactory);
        Thread pigThread = new Thread(pigFactory);

        duckThread.start();
        pigThread.start();
    }
}

vemos que para cada caso creamos nuevas instancias de las clases productoras, y  ya que éstas implementan la interfaz Runnable es posible pasárselas de parámetro al constructor de la clase Thread a la cual básicamente le estaremos diciendo que correr a través de este parámetro.

Finalmente se puede ejecutar el Thread, con una llamada al método start del Thread que recién creamos.

Al finalizar los ciclos definidos en los métodos run() el Thread finaliza y muere, y como java es genial limpia la memoria al salir 🙂

Thread

Ahora bien, lograremos exactamente lo mismo! excepto que esta vez nuestra clase Outlet se vera bastante mas simple y nuestro código sera bastante mas limpio también. Esta es mi forma favorita de hacer las cosas, presten atención.

La clase DuckFactory se vería así:

package com.ricardogeek.threads;

import java.util.Random;

public class DuckFactory extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Pato " + i);

            try {
                Thread.sleep(numeroRandom(1000, 5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static int numeroRandom(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }
}

Similarmente PigFactory:

package com.ricardogeek.threads;

import java.util.Random;

public class PigFactory extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Cerdo " + i);
            try {
                Thread.sleep(numeroRandom(1000, 5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static int numeroRandom(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }

}

Bien la cosa se ve bastante igual a usar Runnable  pero esta vez heredamos la clase Thread! y en lugar de implementar el método run() de Runnable, vamos a hacerle Override al método run() de la clase Thread, de este modo nos ahorramos un parámetro y una instancia a la hora de ejecutar el Thread, y este hará lo que a nosotros se nos antoje.

Finalmente en nuestra clase outlet basta con decirle a nuestros productores que comiencen la diversión!

package com.ricardogeek.threads;

public class Outlet {
    private static final DuckFactory duckFactory = new DuckFactory();
    private static final PigFactory pigFactory = new PigFactory();

    public static void main(String[] args) {
        duckFactory.start();
        pigFactory.start();
    }
}

Clase anonima 1449304937_scull

Esta es mi forma menos favorita de hacer las cosas jovenes, no me gustaría ni siquiera tener que explicarlo porque se que hay gente allá afuera que lo va implementar en producción. Sin embargo lo pongo con la esperanza de que alguien lo lea, y cuando lo vea sea capaz de decirle al que lo intente hacer:

Que ni se te ocurra....
Que ni se te ocurra….

Veamos por que no deben hacerlo:

  • NO se entiende!
  • Fomenta la repetición de código ya que solo se puede usar una vez
  • Al ser clases internas hacen referencia a la clase que las contiene, y esto hace que tenga acceso a sus privados y potencialmente podría romper la encapsulamiento de su programa
  • Es jodido escribir un unit test para esto (todo en la vida debería tener un unit test)

En serio… no lo hagan a menos que sea su última opción!

En este ejemplo no necesitamos ni DuckFactory, ni PigFactory, solo un Outlet monstruoso que no se entiende que se veria mas o menos asi:

package com.ricardogeek.threads;

import java.util.Random;

public class Outlet {


    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Cerdo " + i);

                    try {
                        Thread.sleep(numeroRandom(1000, 5000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Pato " + i);

                    try {
                        Thread.sleep(numeroRandom(1000, 5000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        thread.start();
        thread2.start();
    }

    private static int numeroRandom(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }
}

Ni siquiera voy a explicarlo! solo dire que si esta última te parece la mejor opción leas este libro de pasta a pasta, y si al finalizar aun crees que es la mejor opción… considera dedicarte a otra cosa (consejo amistoso).

Conclusion

No usen clases anonimas! menos para Threads!!! los Threads pueden complicarse mucho, no lo compliquen mas.

como siempre, espero que el post haya sido de su utilidad.

Categorized in: