Concurrencia y multihilo en Kotlin: de la A a la Z
Las aplicaciones modernas requieren cada vez más la ejecución de múltiples tareas simultáneamente. En Kotlin, al igual que en otros lenguajes JVM, el multihilo se implementa tradicionalmente a través de la clase Thread. Sin embargo, el lenguaje ofrece una herramienta mucho más potente y elegante: las corutinas (coroutines). En este artículo, analizaremos todos los aspectos de la concurrencia y el multihilo en Kotlin: desde conceptos básicos hasta técnicas avanzadas.
1. Fundamentos del multihilo: Thread y Runnable
Empecemos con el enfoque clásico. En Kotlin, al igual que en Java, se puede crear un hilo directamente:
fun main() { val thread = Thread { for (i in 1..5) { println("Hilo: $i") Thread.sleep(500) } } thread.start() println("Hilo principal finalizado")}Sin embargo, la gestión manual de hilos tiene desventajas:
- Alto costo general — crear y destruir hilos es costoso.
- Complejidad de sincronización — es fácil caer en una condición de carrera.
- Número limitado de hilos — el sistema solo puede crear un número limitado de hilos.
Para sincronizar el acceso a datos compartidos se utiliza la palabra clave synchronized o los bloqueos ReentrantLock:
class Counter { private var count = 0 @Synchronized fun increment() { count++ }}2. Corutinas: el enfoque moderno para la concurrencia
Las corutinas son hilos ligeros que resuelven los problemas del multihilo clásico. No están vinculadas a hilos del sistema y pueden ejecutarse en un solo hilo, alternando entre tareas.
2.1. Primera corutina
Para trabajar con corutinas, es necesario agregar la dependencia org.jetbrains.kotlinx:kotlinx-coroutines-core. Ejemplo de una corutina simple:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) println("¡Mundo!") } println("Hola,")}Aquí, runBlocking es un puente entre el código normal y las corutinas. launch inicia una nueva corutina y delay es un análogo de Thread.sleep(), pero sin bloquear el hilo.
2.2. Funciones suspend
La palabra clave suspend permite pausar la ejecución de una función sin bloquear el hilo:
suspend fun fetchData(): String { delay(2000L) // simula una solicitud de red return "Datos recibidos"}
fun main() = runBlocking { val result = async { fetchData() } println("Esperando...") println(result.await())}2.3. Concurrencia estructurada
Kotlin admite concurrencia estructurada: todas las corutinas hijas se completan automáticamente antes de que finalice la corutina padre:
fun main() = runBlocking { coroutineScope { launch { delay(1000L) println("Tarea 1") } launch { delay(500L) println("Tarea 2") } } println("Todas las tareas completadas")}3. Técnicas avanzadas: Channel, Flow y Dispatchers
3.1. Canales (Channel)
Los canales permiten intercambiar datos entre corutinas. Son un análogo de una cola:
fun main() = runBlocking { val channel = Channel<Int>() launch { for (i in 1..5) { channel.send(i * i) } channel.close() } for (value in channel) { println(value) }}3.2. Flow: flujos de datos fríos
Flow es un flujo reactivo que emite valores bajo demanda:
fun simpleFlow(): Flow<Int> = flow { for (i in 1..3) { delay(