Saltar al contenido principal

Thread, Runnable y ciclo de vida

En la unidad anterior razonamos sobre intercalaciones. Ahora crearemos tareas sencillas y observaremos en qué hilo se ejecutan, sin confundir una salida concreta con un orden garantizado.

Separar tarea e hilo​

Runnable representa trabajo sin resultado. Thread representa el hilo que puede ejecutarlo:

Runnable tarea = () -> System.out.printf(
"Tarea en %s%n", Thread.currentThread().getName());

Thread trabajador = new Thread(tarea, "trabajador-1");
trabajador.start();

El nombre ayuda a observar el experimento; no determina el orden. Separar la tarea permite reutilizarla y prepara el paso posterior a pools.

start() no es run()​

public class StartVsRun {
public static void main(String[] args) throws InterruptedException {
Runnable tarea = () -> System.out.println(
"Ejecutando en " + Thread.currentThread().getName());

Thread conRun = new Thread(tarea, "hilo-run");
Thread conStart = new Thread(tarea, "hilo-start");

System.out.println("main es " + Thread.currentThread().getName());
conRun.run();
conStart.start();
conStart.join();
}
}

Antes de ejecutarlo, pregunta: ¿qué nombre mostrará cada llamada?

  • conRun.run() es una llamada normal y se ejecuta en el hilo llamador, normalmente main.
  • conStart.start() solicita iniciar un hilo nuevo; ese hilo ejecutará run().

No es correcto decir que run() «no funciona»: ejecuta la tarea, pero no inicia otro hilo. Un mismo objeto Thread solo puede iniciarse una vez; un segundo start() lanza IllegalThreadStateException.

Orden no garantizado​

Runnable mostrar = () -> {
String nombre = Thread.currentThread().getName();
for (int i = 1; i <= 3; i++) {
System.out.println(nombre + ": " + i);
}
};

Thread a = new Thread(mostrar, "A");
Thread b = new Thread(mostrar, "B");
a.start();
b.start();
a.join();
b.join();
System.out.println("Ambos terminaron");

Garantizado: cada hilo conserva el orden 1, 2, 3; tras ambos join(), ambos han terminado.

Posible: los mensajes de A y B pueden intercalarse de distintas formas.

No garantizado: que A termine antes que B. Escribir primero a.join() no impone orden entre los trabajadores; el hilo main espera a A y después comprueba B.

Estados relevantes​

Thread.State describe un snapshot del hilo:

EstadoSignificado básico
NEWCreado, aún no iniciado
RUNNABLEEjecutable o ejecutándose
BLOCKEDEspera entrar en una región sincronizada
WAITINGEspera sin límite temporal a otra acción
TIMED_WAITINGEspera con límite temporal
TERMINATEDTerminó

Entre consultar getState() y mostrarlo, el estado puede haber cambiado. Sirve para diagnosticar y aprender, no para coordinar lógica.

Thread hilo = new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "observable");

System.out.println(hilo.getState()); // NEW en este punto
hilo.start();
System.out.println(hilo.getState()); // snapshot, no valor fijo
hilo.join();
System.out.println(hilo.getState()); // TERMINATED

sleep() solo pausa​

Thread.sleep(...) pausa el hilo actual durante al menos el intervalo solicitado, salvo interrupción. No sincroniza, no garantiza que otro hilo avance y no establece un orden correcto entre tareas.

Nunca lo uses con la idea de «dar tiempo al otro hilo». Si necesitas esperar su terminación, usa join(); otras coordinaciones llegarán después.

Experimento: predicción y evidencia​

Ejecuta StartVsRun varias veces:

  1. predice los nombres de hilo;
  2. registra la salida observada;
  3. explica qué proviene de una llamada normal y qué de start();
  4. repite sin convertir el orden de las líneas en una garantía.

El nombre de conRun.run() sí se razona por semántica: lo ejecuta main. El momento relativo en que aparece el mensaje del nuevo hilo depende de la planificación.

Práctica​

Corrige este programa para que ejecute dos tareas concurrentemente y espere su terminación:

Thread a = new Thread(() -> System.out.println("A"));
Thread b = new Thread(() -> System.out.println("B"));
a.run();
b.run();
System.out.println("Fin");

Solución​

Thread a = new Thread(() -> System.out.println("A"));
Thread b = new Thread(() -> System.out.println("B"));
a.start();
b.start();
a.join();
b.join();
System.out.println("Fin");

Está garantizado que Fin aparece al final. No está garantizado si A o B aparece primero.

Errores habituales​

  • Llamar a run() esperando iniciar otro hilo.
  • Reutilizar un Thread terminado con un segundo start().
  • Interpretar RUNNABLE como «ejecutándose ahora mismo».
  • Usar sleep() como sincronización.
  • Creer que el orden de los join() ordena a los trabajadores.

Crear hilos manualmente sirve para comprender el mecanismo. Cuando haya muchas tareas, administraremos tareas y recursos mediante ExecutorService.