2 - El modelo de hilos en Java: Thread

El API de hilos nativos de Java se basa en la clase Thread y en la interfaz Runnable. Aquí aprenderás a crearlos de forma segura, conocer su ciclo de vida y usar las herramientas básicas para coordinarlos sin sorpresas.

2.1 Crear un hilo con la clase Thread

  • Sobrescribir run(): extiende Thread y coloca la lógica dentro del método run.
  • start() vs run(): start() crea un hilo nuevo y luego invoca run() dentro de él; llamar a run() directamente ejecuta en el mismo hilo actual.
  • Ciclo de vida: NEWRUNNABLE/RUNNINGBLOCKED (o WAITING/TIMED_WAITING) → TERMINATED.
class TrabajoPesado extends Thread {
  @Override
  public void run() {
    System.out.println("[Hilo] Iniciando");
    try {
      Thread.sleep(300); // Simula trabajo
    } catch (InterruptedException e) {
      System.out.println("[Hilo] Interrumpido");
      Thread.currentThread().interrupt();
    }
    System.out.println("[Hilo] Terminando");
  }

  public static void main(String[] args) {
    TrabajoPesado hilo = new TrabajoPesado();
    hilo.start(); // Ejecuta en un hilo aparte
    // hilo.run(); // Ejecutaría en el mismo hilo main
  }
}

2.2 Crear hilos usando Runnable

  • Interfaz funcional: Runnable declara un solo método run(), compatible con expresiones lambda.
  • Composición con lambdas: evita clases anónimas verbosas y permite pasar comportamiento como argumento.
  • Separar tarea y ejecución: defines la tarea con Runnable y decides luego si ejecutarla en un Thread directo o en un Executor.
public class DemoRunnable {
  public static void main(String[] args) throws Exception {
    Runnable tarea = () -> System.out.println("Hola desde " + Thread.currentThread().getName());

    Thread hilo = new Thread(tarea, "worker-1");
    hilo.start();
    hilo.join(); // Espera a que termine
  }
}

2.3 Estados del hilo (Thread.State)

  • NEW: recién creado, aún no se llamó a start().
  • RUNNABLE: listo para ejecutarse o ejecutándose según el planificador.
  • BLOCKED: esperando un monitor (synchronized) para continuar.
  • WAITING: esperando indefinidamente (Object.wait(), join() sin timeout).
  • TIMED_WAITING: esperando con límite de tiempo (sleep, wait(timeout), join(timeout)).
  • TERMINATED: finalizó run() o lanzó una excepción no manejada.

2.4 Herramientas importantes

  • Thread.sleep(): pausa actual sin liberar monitores; maneja InterruptedException y vuelve a marcar el hilo si abortas.
  • Thread.yield(): sugerencia al planificador; no garantiza efecto.
  • Thread.interrupt(): marca un hilo como interrumpido; las operaciones bloqueantes lanzan InterruptedException.
  • join(): espera que otro hilo termine; usa timeouts para evitar bloqueos indefinidos y propaga InterruptedException correctamente.
  • Prioridades (setPriority()): rara vez conviene cambiarlas; los sistemas modernos las ignoran o producen starvation (hilos de menor prioridad no avanzan) si se abusa.
public class PararConInterrupcion implements Runnable {
  @Override
  public void run() {
    while (!Thread.currentThread().isInterrupted()) {
      try {
        hacerTrabajo();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // reestablece bandera
      }
    }
    limpiarRecursos();
  }

  private void hacerTrabajo() throws InterruptedException {
    Thread.sleep(100);
  }

  private void limpiarRecursos() {
    System.out.println("Liberando recursos...");
  }
}

2.5 Cuándo NO usar Thread manualmente

  • Costos de creación: cada hilo reserva memoria para la pila y tarda en arrancar; crear cientos puede saturar el proceso.
  • Gestor manual poco escalable: sin pools ni colas, controlar el flujo se vuelve complejo.
  • Thread explosion: lanzar un hilo por cada tarea masiva agota recursos del SO y degrada el rendimiento; usa ExecutorService o frameworks reactivos.