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, normalmentemain.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:
| Estado | Significado básico |
|---|---|
NEW | Creado, aún no iniciado |
RUNNABLE | Ejecutable o ejecutándose |
BLOCKED | Espera entrar en una región sincronizada |
WAITING | Espera sin límite temporal a otra acción |
TIMED_WAITING | Espera con límite temporal |
TERMINATED | Terminó |
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:
- predice los nombres de hilo;
- registra la salida observada;
- explica qué proviene de una llamada normal y qué de
start(); - 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
Threadterminado con un segundostart(). - Interpretar
RUNNABLEcomo «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.