Java Threads Basico

Un thread es un “hilo” de ejecución de un programa. La JVM permite la ejecución de multiples hilos concurrentemente.

Tenemos dos formas de crear Threads en Java. La primera de ellas extendiendo de java.lang.Thread y la segunda implementando la interfaz java.lang.Runnable

Para el primer caso tenemos este ejemplo, extendiendo de Thread, donde sobreescribimos el metodo run() y luego ejecutamos start() para correr nuestra clase en un thread. Solo puedes llamar a start() una sola vez, si lo vuelves a ejecutar obtendrás un RuntimeException.

package example;

public class BasicThread extends Thread {

    public static void main(String[] args) {
        BasicThread t = new BasicThread();
        t.start();
    }

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

El segundo caso, extendiendo de Runnable, nos da un poco mas de trabajo (aunque no tanto). Solo necesitamos nuestra implementación de Runnable (BasicRunnable) y crear un instancia de Thread que recibirá en su constructor nuestra instancia de BasicRunnable. Luego llamar a start()

En general este diseño, implementar de Runnable, es mucho mejor que el primer caso mostrado arriba.

Observemos bien que nuestra logica la escribimos dentro run() y que el thread lo ejecutamos desde start(). Si llegamos a ejecutar run() nuestra logica correrá pero NO en un hilo separado. Esto suele ser un punto común de confusión.

package example;

public class BasicRunnable implements Runnable {

    public static void main(String[] args) {
        BasicRunnable r = new BasicRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Running ");
    }

}

Nombrando nuestro Thread:

Podemos darle un nombre a nuestro thread de esta manera y conocer el nombre del thread en ejecución así.

package example;

public class BasicRunnable implements Runnable {

    public static void main(String[] args) {

        BasicRunnable r = new BasicRunnable();
        Thread t1 = new Thread(r);
        t1.setName("my thread one");
        Thread t2 = new Thread(r);
        t2.setName("my thread two");

        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Running " + Thread.currentThread().getName());
    }

}

Cuya salida será

    Running my thread one
    Running my thread two

Aunque no le demos un nombre nuestro thread siempre tendrá uno de esta forma

    Running Thread-0
    Running Thread-1

Orden de ejecución:

Debemos tener en claro al construir nuestro código que el orden en el cual los threads son elegidos para correr no esta garantizado.

Veamos este ejemplo donde agregaremos un loop para darle algo mas ‘carga’ y se vea mejor la salida del código.

public class BasicRunnable implements Runnable {

    public static void main(String[] args) {
        BasicRunnable r = new BasicRunnable();
        Thread t1 = new Thread(r);
        t1.setName("my thread one");
        Thread t2 = new Thread(r);
        t2.setName("my thread two");
        Thread t3 = new Thread(r);
        t3.setName("my thread three");
        t1.start();
        t2.start();
        t3.start();
    }

    @Override
    public void run() {
        for (int x = 1; x <= 500; x++) {
            System.out.println("Run by " + Thread.currentThread().getName() + ", x is " + x);
        }

    }
}

Parte de nuestra salida podria ser así, como vemos algo “desordenada”.

    Run by my thread one, x is 2
    Run by my thread three, x is 9
    Run by my thread three, x is 10
    Run by my thread three, x is 11
    Run by my thread three, x is 12
    Run by my thread three, x is 13
    Run by my thread two, x is 2
    Run by my thread two, x is 3
    Run by my thread three, x is 14
    Run by my thread one, x is 3
    Run by my thread one, x is 4
    Run by my thread one, x is 5
    Run by my thread three, x is 15
    Run by my thread two, x is 4

Orden de ejecución:

  1. El constructor de Thread
  2. Thread()
  3. Thread(Runnable target)
  4. Thread(Runnable target, String name)
  5. Thread(String name)

Estados de un Thread

  • New: Cuando hemos creado la instancia de Thread pero todavia NO hemos ejecutado start(), decimos que el thread está en un “new state” y el thread todavia no es considerado “alive”.
  • Runnable: En este estado el thread puede ser elegido para correr, pero todavia no ha sido seleccionado para ejecutarse en un hilo. Este es el primer estado al correr start(). Cuando un thread esta runnable lo consideramos “alive”. Un thread en ejecución podría volver a este estado desde otros estados como “blocked, waiting, o sleeping”.
  • Running: En este estado el thread esta ejecuntado nuestra logica, en nuestro metodo run()
  • Waiting/blocked/sleeping: En estos estados el thread no es elegible para correr pero el thread sigue alive. Es decir no es runnable pero podrá serlo luego. Muy resumidamente un thread pueden entrar en blocked por ejemplo si esta esperando por algun recurso, en sleeping o waiting porque alguna parte del codigo le dijo que entrara en ese estado. La diferencia entre wait y sleep es un thread en ‘wait’ puede ser despertado por otro proceso utilizando notify sin embargo con ‘sleep’ esto no es posible. Usando Thread.sleep(long millis) indicamos el tiempo que deseamos que el thread espere y usando wait() / wait(long timeout) el thread entra en espera hasta el tiempo indicado o hasta que sea ‘despertado’ por notify() o notifyAll()
  • Dead: Un thread es considerado dead cuando ha finalizado run() . Este estado es final.

threads_states

Podemos conocer el estado de un thread con los metodos isAlive() que nos dirá que el thread ha empezado pero no se ha completado el metodo run() . Otro metodo interesante en especial para debug es getState().

Sleeping

Poner en sleep un thread es bastante simple, solo invocamos Thread.sleep() indicando el tiempo en millisegundos.

Importante! Debes tener en cuenta que porque el sleep() termine, esto no significa que el thread vuelve al estado Running. Primero volvera a estado Runnable y el scheduler (el planificador de la jvm) decidira cuando volverlo a Running y continuar ejecutando el codigo restante.

public class BasicRunnable implements Runnable {

    public static void main(String[] args) {
        BasicRunnable r = new BasicRunnable();
        Thread t1 = new Thread(r);
        t1.start();
    }

    @Override
    public void run() {
        try {
            System.out.println("running and sleep");
            Thread.sleep(1 * 60 * 1000); // dormimos el thread 1 minuto
            System.out.println("ok continuing");
        } catch (InterruptedException ex) {
            System.out.println("upss!!!");
        }
    }
}

Prioridades y yield()

Por defecto un hilo corre con prioridad 5, siendo las prioridades entre 1 y 10 donde 1 es la más baja.

Podemos definir la prioridad que deseamos por ejemplo

Thread t = new Thread(r);
    t.setPriority(9);
    t.start();

Una vez definida la prioridad esta no se cambia. Otra vez recalco, que es el valor “deseado” porque como casi todo con los threads esto no esta garantizado. Un hilo de menor prioridad debería ser desplazado por otro de mayor prioridad si este ultimo necesita ejecutarse. Nunca bases tu diseño de codigo basandote en en estas prioridades.

La clase Thread tiene estas tres constantes que definen tres prioridades:

  • Thread.MIN_PRIORITY (1)
  • Thread.NORM_PRIORITY (5)
  • Thread.MAX_PRIORITY (10)

Ahora, veamos, el metodo yield() lo que hace es decirle al hilo en ejecución vuelva al estado “runnable” para darle lugar a otros hilos de la misma prioridad. Este sería el comportamiento supuesto y nuestra intención, pero de nuevo, esto no esta garantizado.

join()

Lo que join() es decirle al thread que hay cierta dependencia con la finalización de otro hilo, es decir que es necesario que termine primero el hilo en ejecución (estado dead) para que empiece el próximo.

El hilo B entrara en estado runnable cuando finalice (dead) el hilo A.

Si A no se completa por alguna causa el hilo B nunca iniciará.

public class JoinExample implements Runnable {

    public static void main(String[] args) {

        Thread t1 = new Thread(new JoinExample(), "t1");
        Thread t2 = new Thread(new JoinExample(), "t2");
        Thread t3 = new Thread(new JoinExample(), "t3");

        t1.start();

        // el segundo hilo t2 no empezara hasta que finalice t1
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.start();

        // el tercer hilo t3 no empezara hasta que finalice t2
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t3.start();

    }

    @Override
    public void run() {
        System.out.println("Running " + Thread.currentThread().getName());
        try {
            for (int i = 0; i < 6; i++) { 
                System.out.print(Thread.currentThread().getName() + "-sleep-" + i + " ");
                Thread.sleep(1000);
            }
            System.out.println("......" );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

En esta salida podemos observar la dependencia que hemos creado.

Vemos que el t2 recien empezará cuando finalice t1. El t3 empezarpa al terminar el t2.

    Running t1
    t1-sleep-0 t1-sleep-1 t1-sleep-2 t1-sleep-3 t1-sleep-4 t1-sleep-5 ......
    Running t2
    t2-sleep-0 t2-sleep-1 t2-sleep-2 t2-sleep-3 t2-sleep-4 t2-sleep-5 ......
    Running t3
    t3-sleep-0 t3-sleep-1 t3-sleep-2 t3-sleep-3 t3-sleep-4 t3-sleep-5 ......

También tenemos un método sobrecargado de join(milisegundo) que le dice al thread que espere cierto tiempo a que termine el primer hilo, pero que una vez trasncurrido ese tiempo empiece igual es decir pase a estado runnable.

Si comentas el código que hace t1.join() verás esta salida

// el segundo hilo t2 no empezara hasta que finalice t1
//      try {
//          t1.join();
//      } catch (InterruptedException e) {
//          e.printStackTrace();
//      

Observa como t1 y t2 corrio a la vez mientras t3 quedó esperando que termine t2

    Running t1
    Running t2
    t1-sleep-0 t2-sleep-0 t2-sleep-1 t1-sleep-1 t2-sleep-2 t1-sleep-2 t2-sleep-3 t1-sleep-3 t2-sleep-4 t1-sleep-4 t2-sleep-5 t1-sleep-5 ......
    ......
    Running t3
    t3-sleep-0 t3-sleep-1 t3-sleep-2 t3-sleep-3 t3-sleep-4 t3-sleep-5 ......

Bien, creo que hasta aquí tenemos una base sobre threads.

Referencias: Oracle Java Doc

Hi! If you find my posts helpful, please support me by inviting me for a coffee :)
Java  Thread 

Ver también