Thread Pool no desenvolvimento móvel — conceitos básicos, pool de threads e princípio de funcionamento

Autor: IT Sectr Publicado: 2026-03-18 Tempo de leitura: 11 min

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 — um pool de threads reutilizáveis para executar tarefas em segundo plano sem a sobrecarga de criar threads.
  • ExecutorService no Android gerencia o pool através de ThreadPoolExecutor com parâmetros configuráveis.
  • OperationQueue no iOS encapsula um thread pool através de maxConcurrentOperationCount.
  • Core pool size — o número mínimo de threads sempre prontos para executar tarefas.
  • Work queue armazena tarefas aguardando um thread livre no pool.

O que é Thread Pool?

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.

Por que o Thread Pool é importante no desenvolvimento móvel

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âmetroSem pool (raw Thread)Com Thread Pool
Criação de threadPara cada tarefaUma vez ao criar o pool
Máximo de threadsIlimitado (risco de OOM)Limitado por core/max pool size
UtilizaçãoBaixa (thread morre após a tarefa)Alta (thread é reutilizada)
GerenciamentoManual (join, interrupt)Automático (ExecutorService)
Consumo de memóriaCresce a cada tarefaFixo

Como o Thread Pool funciona no desenvolvimento móvel?

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 vs Maximum Pool Size

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.

Work Queue e RejectedExecutionHandler

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.

kotlin
// 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)

Thread Pool no Android: ExecutorService

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 no Android

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.

kotlin
// 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()

CoroutineDispatcher como thread pool

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.

Thread Pool no iOS: OperationQueue e GCD

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 e maxConcurrentOperationCount

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.

swift
// 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()

GCD DispatchQueue como thread pool

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).

swift
// 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()
    }
}

Parâmetros de configuração do Thread Pool

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.

Cálculo do tamanho ótimo do pool

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.

Capacidade da fila e comportamento de transbordo

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âmetroRecomendação para móveisJustificativa
corePoolSize2-4Recursos limitados do dispositivo móvel
maxPoolSizecorePoolSize (ou +1-2)Evitar picos de carga da criação de threads
keepAliveTime15-30 segundosLiberação rápida de memória sem criação frequente
Capacidade da fila16-32Equilíbrio entre buffer e risco de OOM
HandlerCallerRunsPolicyBackpressure sem perda de tarefas

Erros comuns ao trabalhar com pool de threads

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 no Thread Pool

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.

kotlin
// 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)
        }
}

Pool não finalizado e vazamentos

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

Como o Thread Pool difere de uma thread normal?

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).

Quantas threads devem estar em um pool para uma aplicação móvel?

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.

O que é CachedThreadPool e por que é perigoso no Android?

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.

É necessário chamar shutdown() para ExecutorService?

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.

OperationQueue e DispatchQueue são pools de threads?

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

  • Thread Pool — um pool de threads reutilizáveis para tarefas em segundo plano, reduzindo a sobrecarga de criar threads.
  • Android usa ThreadPoolExecutor e Executors.newFixedThreadPool(n) com limite explícito de tamanho do pool.
  • iOS fornece OperationQueue com maxConcurrentOperationCount e GCD DispatchQueue com pools de QoS.
  • Core pool size — o número mínimo de threads; maximum pool size — o máximo quando a fila transborda.
  • Deadlock no pool ocorre quando uma tarefa bloqueia aguardando outra tarefa do mesmo pool.
  • CallerRunsPolicy é preferível para aplicações móveis — ele desacelera o chamador sem perder tarefas.
  • Para uma aplicação móvel típica, o tamanho ideal do pool é de 2-4 threads com shutdown() para limpeza.

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.

Discutir o projeto

Leia também