Threads en Java (I)
29 de Octubre del 2016
¡Hola! ¿Preparados para adentraros en el maravilloso mundo de los hilos? ¿Sí, no, quizá? Bueno, ya que entraste aquí al menos inténtalo que no es tan complicado como pueda parecer en un principio.
Antes de que empiece a escribir código sin sentido deberíamos tener claros unos cuantos conceptos como viene siendo costumbre.
Procesos, hilos y multihilos
Un proceso se puede definir como una secuencia de instrucciones en ejecución.
Para los hilos todavía es más simple, un hilo es un proceso que estamos ejecutando en un momento determinado.
Por último tenemos los programas multihilo, al hablar de ellos nos referirnos a la capacidad de existir diferentes hilos ejecutándose ya sea de forma simultanea si tenemos un procesador multinúcleo o de forma consecutiva para un procesador mononúcleo. Para estos últimos tenemos a la maquina virtual de Java gestionando el flujo de trabajo para que tengamos la sensación de que nuestro ordenador está ejecutando muchos procesos a la vez aunque realmente vaya uno detrás de otro.
Clase Thread
Para trabajar con hilos en Java tenemos a la clase Thread, que como sabrás es hilo en inglés. Podemos trabajar de dos formas con esta clase:
- Heredando de ella:
public class Hilo extends Thread {
}
- implementando Runnable
public class MiRunnable implements Runnable {
}
Las dos se trabajan de una forma similar, sin embargo es preferible implementar en vez de heredar, ya que si quisiéramos heredar de otra clase no podríamos al estar limitado a una sola herencia por clase, pero somos capaces de implementar más de una interfaz. Por lo que a partir de ahora todos mis ejemplos serán implementando.
Clase que implementa Runnable
Cuando hemos implementado la interfaz Runnable hemos de sobrescribir el método run() para que se comporte como nosotros queramos, en este ejemplo mostraremos algo tan sencillo como los números del 1 al 5 cuando ejecutemos un hilo desde una instancia de nuestra clase MiRunnable.
@Override
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println(i);
}
}
Clase para probar los Hilos
A la hora de probar nuestra clase MiRunnable deberemos hacer una instancia de ella para luego pasarle esa instancia como parámetro a una instancia de la clase Thread y por último llamar al método start() que llama a su vez al run() que sobrescribimos antes. ¡Cuánto lío! Mucho más claro con un vistazo al código.
public class PruebaHilo {
public static void main(String[] args) {
MiRunnable hilo = new MiRunnable();
Thread thread = new Thread(hilo);
thread.start();
}
}
Métodos
Con respecto a los métodos podemos catalogarlos en los de la clase Thread y los de las instancias que hagamos de la clase.
Para los métodos de la clase tenemos:
Método | Descripción |
---|---|
currentThread() | Devuelve el objeto Thread que está ejecutándose en ese momento |
sleep(long ms) | Pone el hilo en pausa el tiempo en milisegundos que le introduzcamos |
yield() | Pausa el hilo en ejecución para permitir la ejecución de otros |
Ejemplo de currentThread():
// nos devolverá el nombre asignado al método que está ejecutándose en ese momento
@Override
public void run() {
System.out.print(Thread.currentThread().getName());
}
Ejemplo Sleep():
/*
* definimos una espera de 3 segundos a cada hilo, es necesario utilizar
* try/catch ya que es susceptible de lanzar excepciones.
*/
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
El método yield() depende de muchos factores como pueden ser el sistema operativo o el procesador, y sirve para indicar a éste que el hilo actual ya no necesita hacer uso de él.
Métodos de instancia:
Método | Descripción |
---|---|
start() | Indica al intérprete de Java que cree un contexto del hilo del sistema y comience a ejecutarlo |
run() | Este método es llamado por start() después de que el hilo del sistema se haya inicializado |
stop() | Provoca que el hilo se detenga de manera inmediata |
suspend() | Se diferencia de stop() ya que provoca que se detenga la ejecución de un proceso sin destruir el estado de éste |
resume() | Reanuda un hilo suspendido |
join() | Al utilizar este método nos aseguramos que no se ejecute otro hilo hasta que éste finalice |
setPriority() | Asigna la prioridad al hilo indicada por el valor pasado como parámetro |
getPriority() | Devuelve la prioridad del hilo de ejecución en curso |
setName() | Asigna un nombre para identificar a los hilos de una forma más cómoda |
getName() | Devuelve el nombre asignado |
Ejemplos:
// le asignamos nombres a cada hilo y además una prioridad para finalmente inciarlos
public class PruebaHilos {
public static void main(String[] args) {
Hilo hilo = new Hilo();
Thread tA = new Thread(hilo);
Thread tB = new Thread(hilo);
tA.setName("HiloA");
tB.setName("HiloB");
tA.setPriority(1);
tB.setPriority(10);
tA.start();
tB.start();
}
}
/*
* nos aseguramos que Hilo2 no se ejecute hasta que hilo 1 haya finalizado,
* el método join() necesita ir dentro del bloque try/catch
*/
public class PruebaHiloSleep {
public static void main(String[] args) {
HiloSleep he = new HiloSleep();
Thread t1 = new Thread(he);
Thread t2 = new Thread(he);
t1.setName("Hilo1");
t2.setName("Hilo2");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Consejos
Cuando se trata de hilos, muy pocas cosas está garantizadas.
La frase de arriba nos recuerda que los hilos no necesariamente se ejecutarán en el orden en que fueron iniciados o una vez iniciado el hilo no parará hasta terminar su trabajo.
Una vez llamado al método start() en un hilo no puedes volver a llamarlo, si lo haces aparecerá la excepción java.lang.IllegalThreadStateException
.
Hasta aquí el primero de dos post (aquí el segundo) dedicados a los hilos, ¡en pequeñas dosis todo es más fácil!, como siempre no olvidéis que podéis consultar cualquier duda mandándome un correo a iam@jmoral.es o preguntándome directamente desde Twitter @owniz.
Un saludo. ツ