Thread — é a unidade básica de tempo de processador, com sua própria pilha e executando-se independentemente de outras threads. No desenvolvimento mobile, threads são usadas para execução paralela de tarefas, mantendo a interface responsiva durante operações longas. Android suporta java.lang.Thread, Executors e Kotlin Coroutines, iOS — Thread (Objective-C), GCD e OperationQueue. De acordo com Android Thread Documentation, a criação de uma thread nativa exige a alocação de ~1 MB para a pilha pelo sistema operacional.
Principais pontos
Thread (linha de execução) — é uma sequência independente de instruções que o sistema operacional pode agendar em um núcleo da CPU. Cada processo (aplicativo) contém pelo menos uma thread — a Main Thread. Threads adicionais são criadas para execução paralela de tarefas. Cada thread tem sua própria pilha de programa (com variáveis locais), contador de programa (PC) e registradores. A memória heap é compartilhada entre todas as threads do processo.
Em sistemas operacionais móveis, as threads são agendadas por multitarefa preemptiva (preemptive multitasking): o SO pode interromper a execução de uma thread a qualquer momento e passar o controle para outra (context switch). A alternância de contexto é uma operação cara (1-10 microssegundos), pois requer salvar/restaurar registradores da CPU, atualizar o TLB e limpar caches. É por isso que um número excessivo de threads (centenas e milhares) prejudica o desempenho — o SO gasta mais tempo alternando do que executando.
Thread e processo — são conceitos diferentes. Processo é uma instância do aplicativo com memória virtual dedicada. Uma thread dentro do processo compartilha essa memória com outras threads. No Android, cada componente do aplicativo (Activity, Service, BroadcastReceiver) funciona em um processo, mas pode ser executado em threads diferentes. O aplicativo iOS também é um único processo com capacidade de criar threads adicionais via GCD ou Thread.
Cada thread em Java/Kotlin (Android) e NSThread (iOS) passa por cinco estados: New (criada), Runnable (pronta para execução), Running (executando na CPU), Blocked/Waiting (aguardando recurso ou notificação), Terminated (finalizada). As transições entre estados são gerenciadas pelo agendador do SO e por primitivas de sincronização. O desenvolvedor pode influenciar a prioridade da thread (Thread.setPriority()) e seu estado (sleep, join, interrupt).
No Android, a thread entra no estado Blocked ao tentar capturar um monitor ocupado (synchronized), chamar Object.wait() ou Thread.sleep(). No iOS — ao chamar NSCondition.wait(), pthread_cond_wait() ou dispatch_semaphore_wait(). No estado Blocked, a thread não consome CPU, mas ocupa memória (pilha). A thread pode ser interrompida (interrupted) de outra thread, recebendo InterruptedException (Java) ou isCancelled (Kotlin Coroutines).
| Estado | Descrição | Método de transição |
|---|---|---|
| New | Thread criada, mas não iniciada | Construtor Thread() |
| Runnable | Thread pronta para execução, aguardando CPU | thread.start() |
| Running | Thread executando no núcleo da CPU | Agendador do SO |
| Blocked/Waiting | Thread aguardando recurso, monitor ou notificação | synchronized, wait(), sleep() |
| Terminated | Thread finalizou run() ou foi interrompida | run() concluído, interrupt() |
Context switch (alternância de contexto) — operação na qual o SO salva o estado da thread atual (registradores, PC, TLB) e carrega o estado salvo de outra. Em sistemas móveis (Linux + ART, XNU para iOS) o context switch leva de 1 a 10 microssegundos. Se uma thread executa uma tarefa em 100 microssegundos e o context switch leva 5, então 5% do tempo é desperdiçado. Para minimizar o context switch, o iOS usa GCD com work stealing, o Android — pools com fixedThreadCount.
Android evoluiu do java.lang.Thread de baixo nível para as coroutines modernas. Cada nível de abstração oferece mais capacidades com menos sobrecarga. Thread — classe básica, mas sua criação direta não é recomendada: a nova thread não é gerenciada por pool, é difícil de monitorar e cancelar. AsyncTask (deprecated desde API 30) foi um passo à frente, mas sofria de vazamentos de memória e tratamento inconveniente de configurações.
HandlerThread — uma subclasse especial de Thread com Looper, que pode processar uma fila de mensagens. É usado para execução sequencial de tarefas em uma thread de fundo, como escrever dados no Room ou em arquivos. HandlerThread é criado chamando start(), após o qual via Handler(handlerThread.looper) é possível enviar mensagens e Runnable. A chamada handlerThread.quit() para o Looper e finaliza a thread.
// Android: Thread, HandlerThread e Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. Criação direta de Thread (não recomendada)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("Direct thread executed")
})
thread.start()
}
// 2. HandlerThread para tarefas sequenciais em segundo plano
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Execução sequencial em thread de fundo
Thread.sleep(500)
print("HandlerThread: tarefa concluída")
}
// Parar a thread (executado quando as tarefas terminam)
handlerThread.quitSafely()
}
// 3. Executors — pool de threads
fun executorExample() {
val executor = Executors.newFixedThreadPool(4)
for (i in 1..10) {
executor.execute {
print("Task $i on thread ${Thread.currentThread().getName()}")
}
}
executor.shutdown()
}
// 4. Kotlin Coroutines — padrão moderno
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
O exemplo ThreadExample mostra todos os quatro níveis de abstração de threads no Android. A criação direta de Thread — a abordagem mais baixo nível e ineficiente. HandlerThread é útil para tarefas sequenciais em segundo plano. Executors.newFixedThreadPool(4) cria um pool de 4 threads para execução paralela de até 10 tarefas. Kotlin Coroutines com Dispatchers.Default — a maneira moderna, eficiente e segura.
HandlerThread — uma subclasse especializada de Thread com Looper integrado e fila de mensagens. Ele é criado chamando start(), após o qual via Handler(handlerThread.looper) é possível enviar Runnable e mensagens. HandlerThread executa tarefas estritamente sequenciais — a próxima tarefa não começa até a anterior ser concluída. Isso é conveniente para escrever dados no Room ou em arquivos, onde a ordem das operações é crítica. A chamada quitSafely() para o Looper após a conclusão da tarefa atual.
iOS também oferece três níveis de trabalho com threads. Thread (Thread em Swift, NSThread em Objective-C) — API de baixo nível que cria diretamente uma thread nativa. GCD (Grand Central Dispatch) via DispatchQueue — a principal ferramenta para desenvolvedores iOS, que gerencia automaticamente o pool de threads. OperationQueue — abstração de alto nível sobre GCD com suporte a dependências, prioridades e cancelamento.
O uso direto de Thread no desenvolvimento iOS moderno é extremamente raro — o GCD fornece todas as capacidades necessárias com gerenciamento automático de memória e threads. Thread é usado apenas para casos específicos: configurar armazenamento thread-local (threadDictionary), criar RunLoop para thread de fundo ou integração com bibliotecas C que esperam pthread_t.
import Foundation
class ThreadManager {
// 1. Thread (baixo nível)
func createThread() {
let thread = Thread {
// Código executado em nova thread
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// Fila paralela
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD async task")
}
// Barrier para sincronização de escrita
queue.async(flags: .barrier) {
// Acesso exclusivo durante escrita
print("Barrier write: exclusive access")
}
}
// 3. OperationQueue com dependências
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("Downloading...")
}
let process = BlockOperation {
print("Processing...")
}
let save = BlockOperation {
print("Saving...")
}
// Dependências: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// Coleção thread-safe via GCD barrier
class ThreadSafeArray<T> {
private var array: [T] = []
private let queue = DispatchQueue(label: "com.app.concurrent",
attributes: .concurrent)
var count: Int {
return queue.sync { array.count } // concurrent read
}
func append(_ element: T) {
queue.async(flags: .barrier) { // exclusive write
self.array.append(element)
}
}
}
A classe ThreadSafeArray demonstra o padrão Concurrent Read / Exclusive Write via GCD barrier. A leitura via queue.sync{} é executada paralelamente de várias threads. A escrita via queue.async(flags: .barrier) bloqueia todas as outras operações (leitura e escrita) até a conclusão da escrita. Isso é mais eficiente que blocos synchronized, pois não bloqueia leitores enquanto não há escrita.
O uso direto de Thread no iOS é justificado em três casos: para armazenamento thread-local (Thread.current.threadDictionary) — armazenamento de dados vinculados à thread; para criar um RunLoop especial em thread de fundo com performSelector:onThread:; para integração com bibliotecas C/C++ que esperam pthread_t. Em todos os outros casos, o GCD via DispatchQueue é preferível — ele gerencia automaticamente o pool de threads e o consumo de energia.
Race condition (condição de corrida) ocorre quando duas ou mais threads acessam simultaneamente dados compartilhados, e pelo menos uma das threads está escrevendo. O resultado depende da ordem de execução (timing) e é imprevisível. Para prevenir race condition, são usadas primitivas de sincronização. No desenvolvimento mobile, estão disponíveis bloqueios (synchronized, NSLock), operações atômicas (AtomicInteger, propriedades atomic do iOS) e filas (serial queue).
A escolha da primitiva depende do cenário. Para contadores e flags simples, operações atômicas (AtomicInteger, atomic property) são suficientes. Para seções críticas com múltiplas operações — bloqueios (synchronized, NSLock). Para estruturas de dados complexas — serial DispatchQueue ou GCD barrier. Bloqueios são mais fáceis de entender, mas são suscetíveis a deadlocks e livelocks. Filas são mais complexas, porém mais seguras.
// Sincronização em Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — para contadores simples
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — para seções críticas
@Synchronized
fun synchronizedOperation() {
// Apenas uma thread por vez
doWork()
}
// 3. Mutex de coroutines — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// código protegido — thread-safe
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// Exemplo de Deadlock: A bloqueia B, B bloqueia A
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun methodA() = synchronized(lockA) {
Thread.sleep(100)
synchronized(lockB) { print("OK") }
}
fun methodB() = synchronized(lockB) {
Thread.sleep(100)
synchronized(lockA) { print("OK") }
}
}
Counter demonstra três abordagens para sincronização. AtomicInteger.incrementAndGet() — operação atômica sem bloqueios (CAS). @Synchronized — monitor embutido do Java, bloqueia o objeto inteiro. Mutex.withLock — mutex de coroutine, suspende a coroutine em vez de bloquear a thread (mais eficiente). DeadlockExample mostra um deadlock clássico: duas threads capturam bloqueios em ordem diferente.
Thread Pool (pool de threads) — é um conjunto de threads pré-criadas que são reutilizadas para executar tarefas. Em vez de criar uma nova thread para cada tarefa (caro), o pool pega uma thread livre do pool. Se não houver threads livres, a tarefa é colocada em uma fila. O pool gerencia automaticamente o tamanho: novas threads são criadas sob carga de pico, threads ociosas são finalizadas. Isso reduz a sobrecarga de criação de threads dezenas de vezes.
No Android, Executors.newFixedThreadPool(4) cria um pool de 4 threads. Se 10 tarefas chegarem simultaneamente, 4 começam a execução imediatamente, 6 aguardam na fila. Executors.newCachedThreadPool() cria threads conforme necessário (sem limite) e finaliza as ociosas após 60 segundos. Para iOS, o GCD fornece automaticamente pools de filas globais, cujo tamanho corresponde ao número de núcleos da CPU e à carga atual.
Em Kotlin Coroutines, os pools de threads estão ocultos dentro dos dispatchers. Dispatchers.Default usa um pool de tamanho igual ao número de núcleos da CPU (mínimo 2). Dispatchers.IO — 64 threads (suficiente para centenas de tarefas IO-bound, já que a maioria aguardará entrada-saída, sem ocupar CPU). Cada dispatcher dimensiona automaticamente o pool conforme a carga, economizando bateria durante a inatividade.
Perguntas frequentes
Thread — é a unidade básica de execução de código em um aplicativo. Cada processo pode ter múltiplas threads, compartilhando memória, mas com sua própria pilha. No desenvolvimento mobile, threads são usadas para execução paralela de tarefas sem bloquear a UI. Android usa Thread, Executors, HandlerThread e Coroutines. iOS usa Thread, GCD (DispatchQueue) e OperationQueue.
Criar Thread requer alocação de ~1 MB de pilha no Android e ~512 KB no iOS — é uma operação cara. Para 1000 tarefas, a criação direta de 1000 threads exigiria ~1 GB apenas para as pilhas, mais a sobrecarga de context switch. Em vez de Thread, use pools (Executors, GCD) ou coroutines — eles reutilizam threads, reduzindo a sobrecarga dezenas de vezes.
Race condition — comportamento imprevisível quando múltiplas threads acessam simultaneamente dados compartilhados com escrita. Pode ser evitada de três formas: usar tipos atômicos (AtomicInteger), bloqueios (synchronized, NSLock) ou serializar o acesso através de uma fila (DispatchQueue serial, Actor em Kotlin). A melhor prática é minimizar o estado mutável compartilhado e usar imutabilidade.
Thread — é um objeto nativo do sistema, ocupando ~1 MB de pilha e vinculado a um núcleo do SO. Coroutine — é uma unidade de execução leve do Kotlin, que não está vinculada a uma thread específica e pode ser suspensa (suspend) sem bloqueio. Uma única thread pode executar milhares de coroutines. Coroutines são mais eficientes em memória e permitem escrever código assíncrono sem callbacks.
Deadlock se manifesta como um travamento completo do aplicativo sem ANR. No Android, use Thread.getAllStackTraces() para obter um dump das pilhas de todas as threads — duas threads estarão esperando pelos bloqueios uma da outra. No iOS — Thread.callStackSymbols. Ferramentas: Android Studio Profiler (aba Threads), Instruments (iOS, Thread State View). Prevenção: capture bloqueios em ordem fixa, use tryLock com timeout.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também