Muy buenas a todos/as,
hoy tengo entre manos una clase que puede que a alguno de vosotros os sea útil. Vamos a ponernos en situación para ver en que casos podría llegar a ser útil.

A la hora de trabajar con interfaces de usuario no es difícil imaginar el caso de necesitar realizar un trabajo en segundo plano, algo que se compute cuando el usuario realizar cierta acción, y queremos que se pueda seguir trabajando durante ese proceso. El principal problema que podemos encontrarnos aquí es: ¿cómo podemos saber que dicho trabajo ha finalizado?

Podríamos realizar una especie de espera activa, comprobando a cada rato si el trabajo está completo, también podríamos bloquear el hilo principal a la espera de la finalización del trabajo, pero así no se cumpliría la condición de que el usuario pueda continuar usando la interfaz durante todo el proceso.

Aquí os ofrezco una solución, la cual creo que ha quedado bastante elegante, combinando hilos de ejecución, abstracción, interfaces y eventos asíncronos. Os pongo a continuación el código para que podáis verlo y luego comento algunas cosillas sobre el mismo:

import java.util.ArrayList;
import java.util.List;

public abstract class HiloEventos extends Thread {

    private List<IEventoIniciado> oyentesIniciado = new ArrayList<>();
    private List<IEventoFinalizado> oyentesFinalizado = new ArrayList<>();

    public HiloEventos() {
        this(false);
    }

    public HiloEventos(final boolean isDaemon) {
        this.setDaemon(isDaemon);
    }

    public void run () {
        for (IEventoIniciado o : oyentesIniciado) {
            o.iniciado();
        }

        operacionesRun();

        for (IEventoFinalizado o : oyentesFinalizado) {
            o.finalizado();
        }
    }

    public abstract void operacionesRun();

    public void addEscuchadorIniciado(IEventoIniciado iEventoIniciado) {
        oyentesIniciado.add(iEventoIniciado);
    }

    public void removeEscuchadorIniciado(IEventoIniciado escuchador) {
        oyentesIniciado.remove(escuchador);
    }

    public void addEscuchadorFinalizado(IEventoFinalizado escuchador) {
        oyentesFinalizado.add(escuchador);
    }

    public void removeEscuchadorFinalizado(IEventoFinalizado escuchador) {
        oyentesFinalizado.remove(escuchador);
    }

    //

    public interface IEventoIniciado {
        void iniciado();
    }

    public interface IEventoFinalizado {
        void finalizado();
    }
}

Como podéis ver, se trata de una clase abstracta. Eso quiere decir que no se puede instanciar tal cual, para poder lograrlo se ha de implementar en este caso el método que falta. Dicho método en este caso se llama operacionesRun() y dentro de él se encontrarán todas las operaciones que deseemos que realice el hilo en segundo plano.

La clase tampoco tiene nada extraordinario, en el método run() se llama a la función que acabamos de implementar y luego finaliza.

Para saber cuando termina, y también cuando comienza, tenemos a nuestra disposición unos oyentes, que se disparan al comenzar y finalizar la ejecución del hilo hijo. Usamos las interfaces del final de la clase para poder crear los eventos personalizados donde queramos, y lo mejor de todo es que se nos permite usar cuantos necesitemos.

En cuanto a los constructores, es bastante interesante la propiedad daemon. Un daemon o demonio es un hilo que proporciona servicios al resto de hilos en ejecución, llamados hilos de usuario. Por este motivo, los hilos demonios debe permanecer en ejecución mientras haya al menos vivo un hilo de usuario. Es cuando todos los hilos de usuario terminan cuando los hilos demonios pueden cerrarse, y por lo tanto terminar el programa. Aprovechando que su función es prestar servicios, podemos decir que si la ejecución del hilo padre terminar por cualquier circunstancia antes de que termine la ejecución de sus hilos hijos, todos los hilos hijos que sean demonios se cerrarán automáticamente.

A continuación os pongo un ejemplo de cómo usar la clase anterior:

import hiloeventos.HiloEventos.IEventoFinalizado;
import hiloeventos.HiloEventos.IEventoIniciado;

public class Main {

    public static void main(String[] args) {

        System.out.println("EL HILO PADRE COMIENZA");

        HiloEventos hilo1 = new HiloEventos(false) {

            @Override
            public void operacionesRun() {
                System.out.println("ENTRAMOS EN EL RUN DEL HILO HIJO");

                //Operación compleja del hilo hijo
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("SALIMOS DEL RUN DEL HILO HIJO");
            }
        };

        hilo1.addEscuchadorIniciado(new IEventoIniciado() {
            @Override
            public void iniciado() {
                System.out.println("EL HILO HIJO COMIENZA");
            }
        });

        hilo1.addEscuchadorFinalizado(new IEventoFinalizado() {
            @Override
            public void finalizado() {
                System.out.println("EL HILO HIJO TERMINA");
            }
        });

        hilo1.start();

        //Operación compleja del hilo padre
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("EL HILO PADRE TERMINA");
    }
}

Como puede verse, a la hora de instanciar la clase, se nos obliga a implementar el método que falta, e incluimos en él una espera simulando un computo largo. Luego creamos los oyentes para que nos avisen cuando el hilo hijo comienza y finaliza su ejecución. Obviamente podemos realizar cualquier tipo de acción en los oyentes, de la misma forma que si no necesitamos alguno de ellos, no tenemos porqué crearlos.

Recordad lanzar el hilo hijo usando el método start(). Si no llamamos a este método, el hilo no se lanzará.

Tras todo eso, simulamos otro cómputo para el hilo padre. Nos aseguramos que es más corto que el de su hijo para poder hacer algunas pruebas. Las salidas de este programa en concreto son la siguientes:

EL HILO PADRE COMIENZA
EL HILO HIJO COMIENZA
ENTRAMOS EN EL RUN DEL HILO HIJO
EL HILO PADRE TERMINA
SALIMOS DEL RUN DEL HILO HIJO
EL HILO HIJO TERMINA

Si por algún motivo queréis que el hilo hijo sea demonio, tan solo tenéis que cambiar el parámetro del constructor, y obtendréis una salida similar a esta:

EL HILO PADRE COMIENZA
EL HILO HIJO COMIENZA
ENTRAMOS EN EL RUN DEL HILO HIJO
EL HILO PADRE TERMINA

Puede verse como al terminar la ejecución del hilo padre, el hijo ya no imprime más por consola porque se ha cortado su ejecución.

Tan solo tenéis que pensar un momento para que se os ocurran cientos de casos en los que se podría usar una clase como esta. Al ser abstracta cada implementación es distinta, y permite realizar infinitas tareas con tan solo una clase en nuestro proyecto. Imaginad por ejemplo: una instalación en segundo plano, o el cálculo de estadísticas de uso de la aplicación.

Espero que os sea útil y que comentéis.

Saludos,
Lázarus Surazal.

Entradas relacionadas

Perfil
prLázarus logo info
Carlos J. Peláez Rivas (Lázarus Surazal)
Graduado y Máster en Ingeniería Informática por la Universidad de Málaga. Actualmente trabajando como desarrollador de aplicaciones en Java usando Vaadin.
Apasionado de los videojuegos, la música y alguna que otra tecnología, siempre buscando cosas nuevas que aprender y hacer.
Más sobre mi...
Contacto
Notificaciones