Un hilo es una secuencia de ejecución que puede avanzar de forma concurrente con otras.
Hasta ahora, tus programas han sido secuenciales. Como un cocinero que corta la cebolla, luego enciende el fuego y después bate huevos. Si espera sin hacer nada mientras hierve el agua, está bloqueado.
Un equipo puede tener varios núcleos, pero concurrencia y paralelismo no son sinónimos. Varias tareas son concurrentes si progresan durante el mismo periodo; solo son paralelas si se ejecutan físicamente a la vez.
Java representa cada hilo mediante Thread y permite separar la tarea mediante Runnable.
¿Qué es un hilo?
Un hilo mantiene su propia pila de llamadas y comparte el heap con otros hilos del proceso. Al iniciar una aplicación aparece el hilo main, además de otros hilos internos que puede crear la JVM.
Para hacer cosas en paralelo (como descargar un archivo mientras la interfaz gráfica sigue respondiendo), necesitamos crear hilos adicionales. Java nos da dos formas de hacerlo.
Heredar de la clase Thread
Podemos crear una clase que extienda directamente de java.lang.Thread y sobrescribir su método run().
// 1. Definimos el hilo
class MiHilo extends Thread {
@Override
public void run() {
System.out.println("Soy un hilo ejecutándose en paralelo");
}
}
// 2. Lo arrancamos
public class Main {
public static void main(String[] args) {
MiHilo hilo = new MiHilo();
hilo.start(); // ¡Arranca!
}
}
¿Por qué NO se recomienda?
- Herencia simple: En Java solo puedes heredar de una clase. Si tu clase extiende
Thread, ya no puede extender deUsuariooVentana. Gastas tu único cartucho de herencia en algo técnico. - Mal diseño: Mezclas la tarea (el código a ejecutar) con el mecanismo de ejecución (el hilo).
Implementar Runnable
La interfaz Runnable representa una “tarea”. Es una interfaz funcional (un solo método: run()).
Esto nos permite separar QUÉ queremos hacer (la tarea) de QUIÉN lo va a hacer (el hilo).
// 1. Definimos la tarea (Runnable)
class TareaPesada implements Runnable {
@Override
public void run() {
System.out.println("Haciendo cálculos complejos...");
}
}
// 2. Le pasamos la tarea a un Hilo genérico
public class Main {
public static void main(String[] args) {
TareaPesada tarea = new TareaPesada();
Thread hilo = new Thread(tarea); // "Toma, haz esto"
hilo.start();
}
}
Versión moderna (lambdas)
Como Runnable es una interfaz funcional, podemos evitar la clase extra y usar una lambda. Es una forma habitual de expresar tareas breves.
Thread hilo = new Thread(() -> {
System.out.println("Hola desde una Lambda en paralelo!");
System.out.println("Hilo actual: " + Thread.currentThread().getName());
});
hilo.start();
Hilos virtuales y de plataforma
Desde Java 21, Thread puede representar hilos de plataforma o hilos virtuales. Los virtuales los programa la JVM sobre un conjunto reducido de hilos de plataforma y resultan especialmente útiles para muchas tareas que pasan tiempo bloqueadas en E/S.
Thread virtual = Thread.startVirtualThread(() -> {
System.out.println("Hola desde un hilo virtual");
});No hacen que el cálculo use más núcleos por sí solo: para trabajo intensivo de CPU, el límite sigue estando en los núcleos disponibles.
Diferencia entre start() y run()
Esta es la pregunta de entrevista más famosa sobre hilos.
hilo.run(): En un hilo de plataforma creado connew Thread(tarea), es una llamada normal y ejecuta la tarea en el hilo actual. En un hilo virtual, invocarrun()directamente no inicia ni ejecuta su tarea.hilo.start(): Planifica un nuevo hilo y hace que este invoquerun(). En un hilo de plataforma interviene el sistema operativo; en uno virtual, la JVM lo programa sobre sus hilos portadores.
Thread t = new Thread(() -> System.out.println("Hola"));
t.run(); // MAL ❌: Ejecuta "Hola" en el hilo Main (secuencial).
t.start(); // BIEN ✅: Crea un hilo nuevo y ejecuta "Hola" en paralelo.
El ciclo de vida de un hilo
Un hilo no solo “existe”. Pasa por varios estados desde que nace hasta que muere.
NEW: Acabas de crear el Thread, pero aún no ha empezado.
RUNNABLE: Se está ejecutando o está listo para recibir tiempo de CPU; Java agrupa ambas situaciones en este estado.
BLOCKED: Espera para entrar en una sección protegida por un monitor.
WAITING / TIMED_WAITING: Espera otra acción, con o sin límite de tiempo.
TERMINATED: El método run() ha terminado y el hilo no se puede reiniciar.
El caos de la concurrencia
Crear hilos es fácil. Lo difícil es que no sabes cuándo se van a ejecutar.
El Planificador de Hilos (Thread Scheduler) del Sistema Operativo decide a quién le da CPU y cuándo. Puede pausar tu hilo en la mitad de una suma.
// Ejemplo de indeterminismo
Thread t1 = new Thread(() -> System.out.println("A"));
Thread t2 = new Thread(() -> System.out.println("B"));
t1.start();
t2.start();
Salida posible 1: A luego B.
Salida posible 2: B luego A.
Nunca asumas el orden.