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:
- Haciendo una clase que haga la tarea deseada, que implemente la interfaz Runnable
- Haciendo una clase que haga la tarea deseada que herede la clase Thread
- 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
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:
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.
馃榾 eres un dios, te amo <3 Gracias por el aporte.
Muchas gracias Sensei de la programaci贸n en Java!
Thanks Dios del java!
Hola, acabo de leer el articulo y me ha parecido interesante, esta muy bien que hay gente explicando las diferentes maneras de conseguir un mismo objetivo. Solo tengo una duda – en la segunda opci贸n, cuando DuckFactory y PigFactory heredan de la clase Thread creo que te has equivocado y has puesto para PigFactory que implementa la interfaz Runnable, DuckFactory esta bien – extends Thread, pero creo que en PigFactory en la rapidez has puesto lo mismo como en la primera opci贸n. Si me equivoco corr铆geme. Un saludo
Hola Mariana, gracias por apuntar a este error! ya lo he corregido. Se agradece la participaci贸n 馃檪
Que buen post amigo , como lo explicas dan ganas de seguir leyendo , sigue asi , mmm podrias mejorar un poco la pagina web , se ver铆a genial con mas detalles
Gracias a ti por leer 馃檪
El dise帽o del blog ha pasado por muchas etapas (antes era aun mas basico jejeje)
Te recomiendo que complementes este post con este otro:
Sincronizaci贸n de threads
Saludos!
Excelente, es la mejor explicacion que he visto de dormir un hilo, ya que todos comenran que al dormir un hilo este muere y nunca mas se puede ejecutar lo cual con vustro ejemplo queda claro que si se puede; gracias , gran aporte
hi 馃檪 bross 馃檪
Ciao a tutti vengo dall’italia / itawero
ciao 馃檪
Ok. I use translate and will be good ok?
Lo siento tengo que hacer con la tercera opcion.
Hola!, excelente art铆culo, tengo una duda, cuando dices que el thread finaliza y muere, y se limpian las memorias, todas las variables se hacen null?
porque justamente tengo ese problema, sabes si existe alguna forma de evitar esto?