Thread Pool — é um mecanismo de gerenciamento de threads no qual um pool de threads pré-criado é reutilizado para executar tarefas, evitando a sobrecarga de criar e destruir threads. No desenvolvimento móvel, o thread pool é usado para operações em segundo plano: requisições de rede, processamento de imagens, trabalho com bancos de dados. De acordo com a documentação do Google Android (2025), ExecutorService é a maneira recomendada de gerenciar threads em segundo plano no Android. No iOS, OperationQueue e GCD DispatchQueue com filas concorrentes globais desempenham um papel semelhante.
Pontos principais
Thread Pool (pool de threads) — é um padrão arquitetural no qual um número fixo de threads é criado antecipadamente e reutilizado para executar múltiplas tarefas. Em vez de criar uma nova thread para cada operação (o que é caro: cerca de 1 MB de pilha por thread na JVM), as tarefas são colocadas em uma fila e executadas por threads disponíveis do pool. No desenvolvimento móvel, o thread pool é crítico para o desempenho — Android e iOS limitam o número de threads por aplicação.
Criar uma thread é uma operação cara: alocação de pilha, registro no sistema, alternância de contexto. Em dispositivos móveis com recursos limitados, a criação descontrolada de threads leva a OOM (OutOfMemoryError) no Android e throttling no iOS. Thread Pool resolve ambos os problemas: limita o número máximo de threads em execução simultânea e reutiliza threads já criadas. O Google recomenda ExecutorService em vez de raw Thread(), a Apple recomenda OperationQueue em vez de Thread.
| Parâmetro | Sem pool (raw Thread) | Com Thread Pool |
|---|---|---|
| Criação de thread | Para cada tarefa | Uma vez ao criar o pool |
| Máximo de threads | Ilimitado (risco de OOM) | Limitado por core/max pool size |
| Utilização | Baixa (thread morre após a tarefa) | Alta (thread é reutilizada) |
| Gerenciamento | Manual (join, interrupt) | Automático (ExecutorService) |
| Consumo de memória | Cresce a cada tarefa | Fixo |
O thread pool funciona no princípio Produtor-Consumidor: as tarefas (Runnable/Callable) são colocadas em uma fila bloqueante (BlockingQueue). As threads do pool aguardam tarefas na fila e as pegam para execução. Algoritmo: se o número de threads livres for menor que corePoolSize, uma nova thread é criada. Se corePoolSize for atingido, a tarefa é colocada na fila. Se a fila estiver cheia e o número de threads for menor que maximumPoolSize, uma thread adicional é criada. Se maximumPoolSize for excedido, a tarefa é rejeitada através de RejectedExecutionHandler.
Core pool size — o número de threads que são mantidas no pool mesmo quando ociosas. Maximum pool size — o número máximo de threads que podem ser criadas quando a fila transborda. A diferença entre eles são as threads adicionais (overflow) que são criadas temporariamente e finalizadas após o tempo ocioso. Em dispositivos móveis, é recomendado definir corePoolSize igual a maximumPoolSize para evitar picos de carga da criação de threads.
BlockingQueue armazena tarefas aguardando execução. As implementações mais populares: LinkedBlockingQueue (ilimitada), ArrayBlockingQueue (limitada) e SynchronousQueue (sem armazenamento — a tarefa é passada diretamente para uma thread). Quando a fila e o pool estão cheios, RejectedExecutionHandler é acionado. As políticas padrão são: AbortPolicy (lança RejectedExecutionException), CallerRunsPolicy (executa na thread do chamador), DiscardPolicy e DiscardOldestPolicy.
// Criação de Thread Pool no Android
val threadPool = ThreadPoolExecutor(
corePoolSize = 2, // Mínimo 2 threads
maximumPoolSize = 4, // Máximo 4 threads
keepAliveTime = 30L, // Tempo de vida da thread de overflow
unit = TimeUnit.SECONDS,
workQueue = LinkedBlockingQueue<Runnable>(16),
threadFactory = Executors.defaultThreadFactory(),
handler = ThreadPoolExecutor.CallerRunsPolicy()
)
// Envio de tarefas
threadPool.execute {
val result = api.fetchData()
runOnUiThread { showData(result) }
}
// Desligamento do pool
threadPool.shutdown()
// Aguardando a conclusão de todas as tarefas
threadPool.awaitTermination(10, TimeUnit.SECONDS)
O Android fornece várias implementações de thread pool através de java.util.concurrent. Executors — uma fábrica com configurações prontas: newFixedThreadPool(n) (pool fixo), newCachedThreadPool() (ilimitado, threads criadas conforme necessário), newSingleThreadExecutor() (uma única thread — execução sequencial). Para projetos móveis, recomenda-se newFixedThreadPool com um limite razoável (2-4 threads), pois o pool cached pode criar muitas threads.
ThreadPoolExecutor (TPE) — uma implementação completa do ExecutorService com parâmetros configuráveis. No Android, o TPE é usado internamente em AsyncTask, IntentService e JobIntentService. Os parâmetros corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue e RejectedExecutionHandler permitem ajustar o comportamento do pool. Recomendações para Android: corePoolSize = número de núcleos de CPU - 1 (para tarefas IO-bound) ou número de núcleos (para tarefas CPU-bound). Para aplicações típicas: 2-4 threads.
// Configurações prontas do Executors
// 1. Pool fixo de 3 threads
val fixedPool = Executors.newFixedThreadPool(3)
// 2. Pool em cache (não recomendado para móveis)
val cachedPool = Executors.newCachedThreadPool()
// 3. Thread única (serialização)
val singlePool = Executors.newSingleThreadExecutor()
// 4. Agendador (tarefas periódicas)
val scheduler = Executors.newScheduledThreadPool(2)
// Uso com Callable e Future
val future: Future<String> = fixedPool.submit(Callable {
"Result: ${api.call()}"
})
// Obtendo o resultado (bloqueia a thread)
val result = future.get(5, TimeUnit.SECONDS)
// Desligamento do pool
fixedPool.shutdownNow()
As corrotinas Kotlin fornecem CoroutineDispatcher — uma abstração semelhante ao thread pool. Dispatchers.IO usa um pool de 64 threads (limitado). Dispatchers.Default — um pool igual ao número de núcleos de CPU. CoroutineDispatcher não requer desligamento manual e é gerenciado automaticamente. Para ajuste fino, crie um ExecutorCoroutineDispatcher personalizado através de Executors.newFixedThreadPool(2).asCoroutineDispatcher(). As corrotinas não substituem o thread pool, mas o envolvem.
O iOS fornece dois mecanismos principais para gerenciar o thread pool: OperationQueue (API de alto nível construída sobre GCD) e GCD DispatchQueue (API de baixo nível em C). A OperationQueue encapsula um thread pool através da propriedade maxConcurrentOperationCount. Por padrão, a OperationQueue usa um máximo definido pelo sistema (depende da carga do sistema). DispatchQueue.global() fornece uma fila concorrente com um pool de threads do sistema.
OperationQueue gerencia o pool de threads através de maxConcurrentOperationCount. O valor 1 cria uma fila serial (análoga a um pool de thread único). Um valor maior que 1 cria um pool concorrente com o limite especificado. Por padrão, maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (ótimo do sistema, geralmente 4-8 threads). A Operation suporta dependências, prioridades e cancelamento. Cada operação é executada em qualquer thread disponível do pool do sistema.
// OperationQueue com pool de 3 threads
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility
// Criação de operações
let operation1 = BlockOperation {
let data = fetchData(from: url1)
DispatchQueue.main.async { updateUI(data) }
}
let operation2 = BlockOperation {
let data = fetchData(from: url2)
DispatchQueue.main.async { updateUI(data) }
}
// Dependência: operation2 aguarda operation1
operation2.addDependency(operation1)
// Adição à fila
queue.addOperations([operation1, operation2], waitUntilFinished: false)
// Cancelamento de todas as operações
queue.cancelAllOperations()
DispatchQueue — é o pool de threads da Apple. Uma fila concorrente (qos: .utility) usa o pool de threads do sistema, otimizado para a carga atual do dispositivo. Diferentes níveis de QoS (userInteractive, userInitiated, utility, background) mapeiam para diferentes pools com diferentes prioridades. DispatchGroup permite sincronizar várias tarefas. DispatchWorkItem suporta cancelamento e qualityOfService. Para controle refinado, crie filas concorrentes personalizadas através de DispatchQueue(label: qos: attributes: .concurrent).
// GCD DispatchQueue como thread pool
let customQueue = DispatchQueue(
label: "com.app.background",
qos: .utility,
attributes: .concurrent,
autoreleaseFrequency: .workItem
)
// Envio de tarefas ao pool
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }
// DispatchGroup para sincronização
let group = DispatchGroup()
let pool = DispatchQueue.global(qos: .utility)
pool.async(group: group) { fetchData() }
pool.async(group: group) { processImage() }
group.notify(queue: .main) {
self.showResult() // Ambas as tarefas concluídas
}
// Limitando concorrência via semáforo
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
pool.async {
semaphore.wait()
download(url)
semaphore.signal()
}
}
A configuração do thread pool afeta diretamente o desempenho da aplicação. Parâmetros incorretos levam à subutilização da CPU (poucas threads) ou sobrecarga do sistema (muitas). Para aplicações móveis, os valores ótimos diferem dos do lado do servidor devido aos recursos limitados e consumo de energia. Os principais parâmetros são: corePoolSize, maxPoolSize, capacidade da fila e keepAliveTime.
Fórmula para tarefas IO-bound: corePoolSize = número de núcleos de CPU × 2 (threads aguardam E/S). Para tarefas CPU-bound: corePoolSize = número de núcleos de CPU (threads estão constantemente ocupadas com cálculos). Em dispositivos móveis modernos (6-8 núcleos), isso dá 6-8 threads para CPU-bound e 12-16 para IO-bound. Testes práticos mostram que para uma aplicação móvel típica, 3-4 threads são ótimos — mais threads aumentam o consumo de energia sem ganho de desempenho.
O tamanho da fila de trabalho (work queue) determina quantas tarefas podem aguardar execução. Fila ilimitada (LinkedBlockingQueue sem limite) pode levar a OOM com chegada rápida de tarefas. Fila limitada (ArrayBlockingQueue com tamanho fixo) rejeita tarefas quando cheia. Para aplicações móveis, recomenda-se ArrayBlockingQueue com capacidade de 16-32 tarefas. CallerRunsPolicy é o melhor RejectedExecutionHandler para dispositivos móveis: ele desacelera o chamador (backpressure) em vez de perder a tarefa.
| Parâmetro | Recomendação para móveis | Justificativa |
|---|---|---|
| corePoolSize | 2-4 | Recursos limitados do dispositivo móvel |
| maxPoolSize | corePoolSize (ou +1-2) | Evitar picos de carga da criação de threads |
| keepAliveTime | 15-30 segundos | Liberação rápida de memória sem criação frequente |
| Capacidade da fila | 16-32 | Equilíbrio entre buffer e risco de OOM |
| Handler | CallerRunsPolicy | Backpressure sem perda de tarefas |
Desenvolvedores de aplicações móveis frequentemente cometem erros ao usar thread pool que levam a falhas, vazamentos de memória e operação instável. Mais comuns: não chamar shutdown() para ExecutorService, criar um novo pool para cada operação, pool muito grande, deadlock entre tarefas, usar CachedThreadPool no Android.
Deadlock ocorre quando uma tarefa no pool aguarda o resultado de outra tarefa do mesmo pool, mas todas as threads estão ocupadas esperando. Exemplo: a tarefa A envia a tarefa B para o mesmo pool e chama future.get() — se o pool estiver esgotado, a tarefa A aguarda a tarefa B, e a tarefa B não pode executar porque não há threads livres. Solução: use pools separados para diferentes níveis de tarefas ou callbacks assíncronos em vez de .get() bloqueante.
// Deadlock no Thread Pool
val pool = Executors.newFixedThreadPool(1)
// Tarefa A aguarda tarefa B — deadlock!
val futureA = pool.submit {
// Esta tarefa nunca será executada
val futureB = pool.submit { 42 }
futureB.get() // Bloqueia para sempre
}
// Solução: pools separados
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()
workerPool.submit {
callbackPool.submit {
// Executando em pool separado — deadlock impossível
}
}
// Ou use CompletableFuture
workerPool.submit {
CompletableFuture
.supplyAsync { 42 }
.thenAccept { result ->
println(result)
}
}
Um ExecutorService criado em uma Activity deve ser finalizado em onDestroy(). Se isso não for feito, as threads ficarão penduradas na memória mesmo após a destruição da Activity. Solução: mantenha o pool no escopo da Application ou ViewModel, não na Activity. Para corrotinas, use viewModelScope ou lifecycleScope. Se o pool for criado dentro de uma Activity, certifique-se de chamar pool.shutdown() em onDestroy(). Para testes, use es.shutdownNow() para parada imediata.
Perguntas frequentes
Thread Pool reutiliza threads já criadas para executar várias tarefas. Uma thread normal (raw Thread) é criada, executa uma tarefa e é destruída. Criar uma thread consome cerca de 1 MB de memória e ~1 ms de tempo. Thread Pool reduz a sobrecarga, limita o número máximo de threads e fornece uma API para gerenciamento (shutdown, awaitTermination).
Para uma aplicação móvel típica, 2-4 threads é o ideal. Para tarefas CPU-bound — o número de núcleos de CPU. Para tarefas IO-bound — número de núcleos × 2. Mais threads aumentam o consumo de energia e a alternância de contexto sem ganho de desempenho. No Android, use Process.availableProcessors() para determinar os núcleos. No iOS — ProcessInfo.processInfo.processorCount.
CachedThreadPool cria threads conforme necessário e reutiliza as existentes. O problema: ele não limita o número máximo de threads. Se 100 tarefas chegarem simultaneamente, 100 threads serão criadas. Isso leva a OOM no Android (cada thread ~1 MB). Use newFixedThreadPool(n) com um limite explícito. CachedThreadPool só é aceitável para tarefas curtas de burst com volume pequeno garantido.
Sim, se o pool não pertencer a um contêiner gerenciado (como corrotinas). shutdown() para de aceitar novas tarefas e finaliza as threads após completar as atuais. Sem shutdown(), as threads ficam penduradas na memória e a aplicação não termina. Para Activity, chame em onDestroy(). Para ViewModel, use coroutineScope. Finalizar o pool é uma parte obrigatória do gerenciamento de recursos, semelhante a fechar um Cursor ou InputStream.
Sim, OperationQueue e DispatchQueue são pools de threads fornecidos pelo iOS. A OperationQueue limita a concorrência através de maxConcurrentOperationCount. DispatchQueue.global() usa o pool de threads do sistema sem controle direto. Ao contrário do Java ThreadPoolExecutor, você não gerencia corePoolSize ou capacidade da fila — o sistema otimiza o pool automaticamente com base na carga atual e no consumo de energia do dispositivo.
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