Concorrência e Multithreading em Kotlin: da teoria à prática
Aplicativos modernos exigem operação rápida e responsiva. Seja para carregar dados da rede, processar grandes conjuntos de dados ou trabalhar com a interface do usuário — não há como evitar concorrência e multithreading. Kotlin, sendo uma linguagem moderna para JVM, Android e projetos multiplataforma, oferece ferramentas flexíveis para resolver essas tarefas. Neste artigo, analisaremos as principais abordagens: desde threads clássicas até coroutines leves, e mostraremos como escrever código eficiente.
1. Threads em Kotlin: abordagem clássica
Threads são o mecanismo básico de execução paralela, herdado do Java. Em Kotlin, você pode criar threads da mesma forma que em Java, mas com uma sintaxe mais concisa.
Exemplo simples de inicialização de uma thread:
fun main() { thread { println("Thread iniciada: ${Thread.currentThread().name}") Thread.sleep(1000) println("Thread finalizada") } println("Thread principal: ${Thread.currentThread().name}") Thread.sleep(2000)}No entanto, as threads têm desvantagens: são pesadas (cada thread consome cerca de 1 MB de memória), seu número é limitado e a troca de contexto é cara. Para aplicativos modernos com milhares de tarefas paralelas, as threads não são a melhor escolha. É aqui que as coroutines vêm em socorro.
2. Coroutines: alternativa leve
Coroutines são o principal recurso do Kotlin para concorrência. Elas funcionam sobre as threads, mas usam menos recursos. Uma única thread pode executar milhares de coroutines, suspendendo e retomando-as sem bloqueio.
Para começar, adicione a dependência em build.gradle.kts:
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")}Exemplo de uma coroutine simples:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) // atraso não bloqueante println("Mundo da coroutine!") } println("Olá da coroutine principal") delay(2000L)}Aqui, launch inicia uma nova coroutine, e delay a suspende sem bloquear a thread. runBlocking é usado para bloquear a thread principal até que todas as coroutines terminem (geralmente usado apenas em main e testes).
2.1. Funções Suspend
A palavra-chave suspend permite suspender a execução de uma função sem bloquear a thread. Esta é a base da programação assíncrona em Kotlin.
suspend fun fetchUser(): String { delay(500L) // simulação de requisição de rede return "User123"}
fun main() = runBlocking { val user = fetchUser() println("Usuário obtido: $user")}2.2. Async e Deferred para tarefas paralelas
Se você precisa executar várias tarefas em paralelo e aguardar seus resultados, use async e Deferred:
fun main() = runBlocking { val time = measureTimeMillis { val user = async { fetchUser() } val profile = async { fetchProfile() } println("Resultados: ${user.await()}, ${profile.await()}") } println("Tempo de execução: $time ms")}
suspend fun fetchUser(): String { delay(500) return "User"}
suspend fun fetchProfile(): String { delay(500) return "Profile"}Ambas as tarefas são executadas em paralelo, reduzindo o tempo total.
3. Dispatchers de Coroutines
Dispatchers determinam em quais threads as coroutines serão executadas. Os principais tipos são:
Dispatchers.Default— para tarefas intensivas de CPU (padrão).Dispatchers.IO— para operações de entrada/saída (rede, arquivos).Dispatchers.Main— para a thread de UI (Android, JavaFX).