¿Cómo demorar la ejecución de un método en Java 2/2?

En la entrada anterior hablamos sobre como demorar la ejecución de un programa Java usando Thread.sleep, vimos un ejemplo y las implicaciones que tiene usar esta forma de “dormir” la ejecución. En esta ocasión veremos una segunda forma de implementar este tipo de situación.

ExecutorService.schedule

Vimos en la entrada anterior que Thread.Sleep tiene el problema de que “tranca” la ejecución del hilo actual. Sin embargo pueden darse situaciones en las que no sea estrictamente necesario dormir la ejecución del hilo en curso, sino que queramos simplemente hacer una llamada a un método que haga X cosa al cabo de un tiempo t. En este caso el esquema sería así:

Funcionamiento de excecutor.schedule

La idea principal es, sacar un hilo del actual, esperar un tiempo t, por ejemplo t=3s y luego hacer la acción.

Vamos al código de como hacer esto:

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.schedule(Clase::Metodo, time, timeUnit);
executorService.shutdown();

En el código anterior :

  • Clase::Metodo: Es el método que queremos ejecutar cuando pase time.
  • time: Es el valor del tiempo.
  • timeUnit: es la unidad de tiempo en la que expresamos el número. Cualquier valor es válido de los disponibles en java.util.concurrent.TimeUnit

Veamos ahora un ejemplo más completo que podemos reusar en nuestros proyectos, digamos más genérico.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class SleeperWithExecutor {
    // Open new thread and run after time in timeUnit
    public void runWithDelay(Runnable runnable, long time, TimeUnit timeUnit) {
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.schedule(runnable, time, timeUnit);
        executorService.shutdown();
    }
}

Como vemos, esta clase es independiente, tenemos un método llamado runWithDelay, y el primer parámetro es cualquier cosa que venga de Runnable.

Para usar esta clase veamos el siguiente ejemplo:

import java.util.concurrent.TimeUnit;
public class ThreadSleepWithExecutor {
    public static SleeperWithExecutor sleeperWithExecutor;
    public ThreadSleepWithExecutor(){
        sleeperWithExecutor = new SleeperWithExecutor();
    }
    public void doTaskX(){
        System.out.println("Doing task X");
    }
    public static void main(String[] args) {
        ThreadSleepWithExecutor threadSleepWithExecutor = new ThreadSleepWithExecutor();
        long sleepTime = 2000;
        System.out.println("Step 1");
        sleeperWithExecutor.runWithDelay(() -> threadSleepWithExecutor.doTaskX(), sleepTime, TimeUnit.MILLISECONDS);
        Thread.getAllStackTraces().keySet().forEach((t) -> System.out.println(t.getName()));
        System.out.println("Step 2");
    }
}

No hay mucha complejidad en el código anterior. La instrucción :

Thread.getAllStackTraces().keySet().forEach((t) -> System.out.println(t.getName()));

Solo nos servirá para imprimir la lista de hilos, tiene solo fines de debug, corramos el programa anterior y veamos la salida.

Step 1
main
Monitor Ctrl-Break
Attach Listener
Reference Handler
Signal Dispatcher
Common-Cleaner
Finalizer
pool-1-thread-1
Step 2
Doing task X

Puntos interesantes:

  • main: Es el hilo principal de la aplicación.
  • pool-1-thread-1: Es el nuevo hilo que se creó para ejecutar el método doTaskX.
  • Primero se imprimió “Step 2”, lo que nos dice que el hilo “main” siguió su ejecución.
  • “Doing task X”, es lo último que se imprimió al correr el tiempo esperado, sin bloquear el hilo principal.

Espero te sea útil el artículo. Puedes ver la Parte 1 también que es, digamos, la más conocida de este tema. El código está en GitHub.