Skip to content

3 Runnable

La interfaz Runnable

La técnica anterior de definir una clase que extiende de Thread tiene un inconveniente: como sabemos Java no permite la herencia múltiple, por lo que si queremos que extienda de Thread no podrá extender de ninguna otra clase. Para solucionar este problema, Java nos proporciona la interfaz Runnable que representa el comportamiento de una tarea (un conjunto de líneas de código) que pueda ser ejecutada en un hilo de ejecución.

package java.lang;

@FunctionalInterface
public interface Runnable {
    void run();
}

De esta manera, en vez de definir una clase que extienda de Thread, definiremos una clase que implemente la interfaz Runnable, permitiéndonos que la clase extienda de cualquier otra clase que nos interese.

La interfaz Runnable posee un único método: el método run(), que sobrescribiremos para indicar el código que debe ser ejecutado. De hecho, la clase Thread implementa la interfaz Runnable, y por eso dispone del método run().

Sin embargo, un objeto de una clase que implemente Runnable no puede ejecutarse por sí sólo, sino que debe hacerlo dentro de un objeto Thread. Para que esto sea posible, la clase Thread nos proporciona un constructor que recibe como parámetro el objeto Runnable (la tarea) que queremos que ejecute dicho hilo cuando se inicie su ejecución.

Así, definiremos una clase que represente la "tarea" y que implementará la interfaz Runnable:

class MyRunnable implements Runnable {

    // ...

    @Override
    public void run() {
        // ...
    }

}

Para ejecutar la tarea en un nuevo hilo de ejecución haremos:

public class Main {

    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }

}

Dado que la interfaz Runnable corresponde a una interfaz funcional, es decir, aquella que tiene un único método abstracto, si estamos empleando Java 8+ podemos usar una expresión lambda o una referencia a método para representar el objeto Runnable. Por ejemplo:

public class Main {

    public static void main(String[] args) {
        new Thread(() -> {
            // ...
        }).start();
    }

}

Debemos tener en cuenta que el método si queremos pasar argumentos al código que debe ejecutarse dentro de run() deberemos pasarlos como argumentos del constructor de la clase que implemente Runnable o mediante un setter, de manera que el argumento recibido se establezca como valor de un campo definido en tal clase. Estas dos técnicas son incompatibles con el empleo de lambdas, dadas las características de éstas.

La técnica de pasar argumento mediante el constructor se describe en el Proyecto Runnable que desarrollaremos a continuación.

Acceso al hilo desde el Runnable

Thread.currentThread() retorna el objeto Thread en el que nos estamos ejecutando

Si desde el objeto Runnable necesitamos acceder a información sobre el hilo de ejecución en el que se está ejecutando, podemos usar el método estático Thread.currentThread(), que retorna el objeto Thread en el que está ejecutando dicha línea de código.

En general, el uso de la interfaz Runnables es más flexible que heredar de Thread, ya que, como estudiaremos más adelante, podremos usarlos en otras clases Java, como por ejemplo con los executors (ejecutores).

Un último aspecto curioso es que el método run() de la interfaz Runnable no retorna nada. Entonces ¿cómo podemos hace que un hilo secundario calcule un resultado y lo devuelva? No podemos hacer que lo retorne, pero sí que podemos almacenar el resultado en alguna variable compartida entre el hilo llamador y el hilo secundario, o incluso definir un campo en el Runnable y almacenar en él el resultado. Cuando termine la ejecución del hilo secundario el hilo que lo inició podrá acceder al resultado a través del campo del Runnable.

Proyecto Runnable

En el siguiente proyecto crearemos una aplicación que muestra la tabla de multiplicar de los números desde el 1 al 10, usando para cada tabla un hilo de ejecución distinto. Para representar la tabla de multiplicar de un número definiremos una clase que implemente la interfaz Runnable.

El código corresponde a las clases descritas en los ejemplos anterior.

public class Main {

    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            new Thread(new MultiplicationTable(i)).start();
        }
    }

}
class MultiplicationTable implements Runnable {

    private final int number;

    MultiplicationTable(int number) {
        this.number = number;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.printf("%s: %d * %d = %d\n", Thread.currentThread()
                    .getName(), number, i, i * number);
        }
    }

}

Al ejecutar el proyecto podemos comprobar como el orden en que se muestran las líneas de las diferentes tablas de multiplicar no es igual al de un programa con un único hilo, e incluso puede ser diferentes en distintas ejecuciones.