Flujos, gorutinas y async/await en Java: escribiendo código concurrente correctamente

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Concurrencia en Java: desde los flujos clásicos hasta gorutinas y async/await

\n\n

Java es un lenguaje que desde su nacimiento ha sabido trabajar con múltiples tareas simultáneamente. Pero el mundo no se detiene. Hoy hablaremos sobre la concurrencia en Java — desde los clásicos flujos (Thread) hasta los flujos virtuales (gorutinas de Project Loom) y el enfoque asíncrono con async/await (usando CompletableFuture como ejemplo).

\n\n

Si programas en Java y quieres que tu servidor no se "ralentice" bajo carga, mientras el código se mantiene legible, este artículo es para ti. ¡Allá vamos!

\n\n

¿Por qué es importante la concurrencia para el desarrollador Java moderno?

\n\n

Imagina que tu sitio web procesa 10 000 solicitudes por segundo. Si cada solicitud se ejecuta secuencialmente, los usuarios esperarán una eternidad. La concurrencia en Java permite ejecutar múltiples tareas "simultáneamente", utilizando eficientemente los recursos del procesador.

\n\n

Las aplicaciones modernas no son solo CRUD. Son microservicios, procesamiento de flujos de datos, WebSocket, análisis en tiempo real. Sin comprender el multihilo en Java, corres el riesgo de escribir código que fallará con OutOfMemoryError o funcionará como un caracol.

\n\n

Nivel 1: Flujos clásicos (Thread) — el fundamento

\n\n

Un flujo en Java es la abstracción de más bajo nivel para ejecutar código en paralelo. Cada flujo es un "proceso ligero" independiente dentro del sistema operativo.

\n\n

Así se ve el ejemplo más simple de creación de un flujo:

\n\n
public class ThreadExample {\n    public static void main(String[] args) {\n        Thread thread = new Thread(() -> {\n            for (int i = 0; i < 5; i++) {\n                System.out.println("Hola desde el flujo: " + i);\n                try {\n                    Thread.sleep(1000);\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                }\n            }\n        });\n        thread.start();\n        System.out.println("El flujo principal continúa trabajando");\n    }\n}
\n\n

Ventajas:

\n
    \n
  • Simplicidad de creación — new Thread(() -> { ... }).start();
  • \n
  • Control directo del ciclo de vida.
  • \n
\n\n

Desventajas:

\n
    \n
  • Crear un flujo es una operación costosa (gastos generales de pila, cambio de contexto).
  • \n
  • Limitación en cantidad: 10 000 flujos pueden matar tu JVM.
  • \n
  • Complejidad de sincronización: deadlock, race condition.
  • \n
\n\n

El enfoque clásico es bueno para tareas pequeñas, pero para aplicaciones de alta carga no es adecuado. Aquí es donde los flujos virtuales en Java vienen al rescate.

\n\n

Nivel 2: Flujos virtuales (Project Loom) — gorutinas en Java

\n\n

Si has oído hablar de las gorutinas en Go, los flujos virtuales de Java son casi lo mismo. Aparecieron en Java 21 (Project Loom) y cambian completamente las reglas del juego.

\n\n

Los flujos virtuales son flujos "ligeros" gestionados por la JVM, no por el sistema operativo. Se pueden crear por millones y consumen un mínimo de memoria.

\n\n

Ejemplo de creación de un flujo virtual:

\n\n
public class VirtualThreadExample {\n    public static void main(String[] args) throws InterruptedException {\n        // Creamos 100 000 flujos virtuales\n        var threads = new Thread[100_000];\n        for (int i = 0; i < threads.length; i++) {\n            threads[i] = Thread.startVirtualThread(() -> {\n                // Simulación de trabajo con I/O bloqueante\n                try {\n                    Thread.sleep(1000);\n                } catch (InterruptedException e) {\n                    Thread.currentThread().interrupt();\n                }\n            });\n        }\n        for (Thread t : threads) {\n            t.join();\n        }\n        System.out.println("¡Todos los flujos virtuales han terminado!");\n    }\n}
\n\n

Increíble, ¿verdad?

Blogs

Book Recommendations