Thread Pool in mobiele ontwikkeling — basis, threadpool en werkingsprincipe

Auteur: IT Sectr Gepubliceerd: 2026-03-18 Leestijd: 11 min

Thread Pool — dit is een mechanisme voor threadbeheer waarbij een vooraf gemaakte pool van threads wordt hergebruikt om taken uit te voeren, waardoor de overhead van het maken en vernietigen van threads wordt vermeden. In mobiele ontwikkeling wordt de threadpool gebruikt voor achtergrondbewerkingen: netwerkverzoeken, beeldverwerking, werken met databases. Volgens Google Android Documentation (2025) is ExecutorService de aanbevolen manier om achtergrondthreads in Android te beheren. In iOS vervullen OperationQueue en GCD DispatchQueue met globale gelijktijdige wachtrijen een vergelijkbare rol.

Belangrijkste punten

  • Thread Pool — een pool van herbruikbare threads voor het uitvoeren van achtergrondtaken zonder overhead voor het maken van threads.
  • ExecutorService in Android beheert de pool via ThreadPoolExecutor met configureerbare parameters.
  • OperationQueue in iOS kapselt de threadpool in via maxConcurrentOperationCount.
  • Core pool size — het minimale aantal threads dat altijd klaar is om taken uit te voeren.
  • Work queue slaat taken op die wachten op een vrije thread in de pool.

Wat is Thread Pool?

Thread Pool (threadpool) — is een architectuurpatroon waarbij een vast aantal threads vooraf wordt gemaakt en hergebruikt voor het uitvoeren van meerdere taken. In plaats van een nieuwe thread te maken voor elke bewerking (wat kostbaar is: ongeveer 1 MB stack per thread in de JVM), worden taken in een wachtrij geplaatst en uitgevoerd door vrije threads uit de pool. In mobiele ontwikkeling is de threadpool cruciaal voor prestaties — Android en iOS beperken het aantal threads per app.

Waarom Thread Pool belangrijk is in mobiele ontwikkeling

Het maken van een thread is een dure bewerking: stacktoewijzing, registratie in het systeem, contextwisseling. Op mobiele apparaten met beperkte resources leidt onbeheerste threadcreatie tot OOM (OutOfMemoryError) op Android en throttling op iOS. Thread Pool lost beide problemen op: het beperkt het maximale aantal gelijktijdig werkende threads en hergebruikt reeds gemaakte threads. Google beveelt ExecutorService aan in plaats van raw Thread(), Apple beveelt OperationQueue aan in plaats van Thread.

ParameterZonder pool (raw Thread)Met Thread Pool
Thread makenVoor elke taakEenmalig bij het maken van de pool
Maximum threadsOnbeperkt (OOM risico)Beperkt door core/max pool size
BenuttingLaag (thread sterft na taak)Hoog (thread wordt hergebruikt)
BeheerHandmatig (join, interrupt)Automatisch (ExecutorService)
GeheugengebruikNeemt toe met elke taakVast

Hoe werkt Thread Pool in mobiele ontwikkeling?

De threadpool werkt volgens het Producer-Consumer-principe: taken (Runnable/Callable) worden in een blokkerende wachtrij (BlockingQueue) geplaatst. Threads uit de pool wachten op taken in de wachtrij en nemen ze op voor uitvoering. Algoritme: als er minder vrije threads zijn dan corePoolSize, wordt een nieuwe thread gemaakt. Als corePoolSize is bereikt, wordt de taak in de wachtrij geplaatst. Als de wachtrij vol is en er minder threads zijn dan maximumPoolSize, wordt een extra thread gemaakt. Bij overschrijding van maximumPoolSize wordt de taak geweigerd via RejectedExecutionHandler.

Core Pool Size vs Maximum Pool Size

Core pool size — het aantal threads dat in de pool wordt gehouden, zelfs in rust. Maximum pool size — het maximale aantal threads dat kan worden gemaakt bij overloop van de wachtrij. Het verschil ertussen zijn extra (overflow) threads die tijdelijk worden gemaakt en eindigen na een inactiviteitstime-out. Op mobiele apparaten wordt aanbevolen om corePoolSize gelijk te stellen aan maximumPoolSize om piekbelastingen bij het maken van threads te voorkomen.

Work Queue en RejectedExecutionHandler

BlockingQueue slaat taken op die wachten op uitvoering. De meest populaire implementaties: LinkedBlockingQueue (onbeperkt), ArrayBlockingQueue (beperkt) en SynchronousQueue (zonder opslag — taak wordt direct aan een thread doorgegeven). Bij overloop van de wachtrij en de pool wordt RejectedExecutionHandler geactiveerd. Standaardbeleid: AbortPolicy (gooit RejectedExecutionException), CallerRunsPolicy (voert uit in de thread van de afzender), DiscardPolicy en DiscardOldestPolicy.

kotlin
// Thread Pool aanmaken in Android
val threadPool = ThreadPoolExecutor(
    corePoolSize = 2,        // Minimaal 2 threads
    maximumPoolSize = 4,     // Maximaal 4 threads
    keepAliveTime = 30L,     // Levensduur overflow-thread
    unit = TimeUnit.SECONDS,
    workQueue = LinkedBlockingQueue<Runnable>(16),
    threadFactory = Executors.defaultThreadFactory(),
    handler = ThreadPoolExecutor.CallerRunsPolicy()
)

// Taken verzenden
threadPool.execute {
    val result = api.fetchData()
    runOnUiThread { showData(result) }
}

// Pool afsluiten
threadPool.shutdown()
// Wachten op voltooiing van alle taken
threadPool.awaitTermination(10, TimeUnit.SECONDS)

Thread Pool in Android: ExecutorService

Android biedt verschillende implementaties van threadpools via java.util.concurrent. Executors — een fabriek met kant-en-klare configuraties: newFixedThreadPool(n) (vaste pool), newCachedThreadPool() (onbeperkt, threads worden indien nodig gemaakt), newSingleThreadExecutor() (één thread — sequentiële uitvoering). Voor mobiele projecten wordt newFixedThreadPool met een redelijke limiet (2-4 threads) aanbevolen, omdat een cached pool te veel threads kan maken.

ThreadPoolExecutor in Android

ThreadPoolExecutor (TPE) — de volledige implementatie van ExecutorService met configureerbare parameters. In Android wordt TPE gebruikt binnen AsyncTask, IntentService en JobIntentService. De parameters corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue en RejectedExecutionHandler maken het mogelijk om het gedrag van de pool fijn af te stemmen. Aanbevelingen voor Android: corePoolSize = aantal CPU-kernen - 1 (voor IO-bound taken) of aantal kernen (voor CPU-bound taken). Voor typische apps — 2-4 threads.

kotlin
// Kant-en-klare Executors-configuraties
// 1. Vaste pool met 3 threads
val fixedPool = Executors.newFixedThreadPool(3)

// 2. Gecachte pool (niet aanbevolen voor mobiel)
val cachedPool = Executors.newCachedThreadPool()

// 3. Enkele thread (serialisatie)
val singlePool = Executors.newSingleThreadExecutor()

// 4. Planner (periodieke taken)
val scheduler = Executors.newScheduledThreadPool(2)

// Gebruik met Callable en Future
val future: Future<String> = fixedPool.submit(Callable {
    "Result: ${api.call()}"
})
// Resultaat ophalen (blokkeert thread)
val result = future.get(5, TimeUnit.SECONDS)

// Pool afsluiten
fixedPool.shutdownNow()

CoroutineDispatcher als threadpool

Kotlin-coroutines bieden CoroutineDispatcher — een abstractie vergelijkbaar met een threadpool. Dispatchers.IO gebruikt een pool van 64 threads (beperkt). Dispatchers.Default — een pool gelijk aan het aantal CPU-kernen. CoroutineDispatcher vereist geen handmatige shutdown en wordt automatisch beheerd. Voor fijnafstemming maakt u uw eigen ExecutorCoroutineDispatcher via Executors.newFixedThreadPool(2).asCoroutineDispatcher(). Coroutines vervangen de threadpool niet, maar omhullen deze.

Thread Pool in iOS: OperationQueue en GCD

iOS biedt twee hoofdmechanismen voor het beheren van de threadpool: OperationQueue (high-level API gebaseerd op GCD) en GCD DispatchQueue (low-level C-API). OperationQueue kapselt de threadpool in via de eigenschap maxConcurrentOperationCount. Standaard gebruikt OperationQueue system-defined maximum (afhankelijk van de systeembelasting). DispatchQueue.global() biedt een gelijktijdige wachtrij met de systeem-threadpool.

OperationQueue en maxConcurrentOperationCount

OperationQueue beheert de threadpool via maxConcurrentOperationCount. Waarde 1 creëert een seriële wachtrij (vergelijkbaar met single thread pool). Waarde groter dan 1 — een gelijktijdige pool met de opgegeven limiet. Standaard is maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (systeemoptimum, meestal 4-8 threads). Operation ondersteunt afhankelijkheden, prioriteiten en annulering. Elke bewerking wordt uitgevoerd op een vrije thread uit de systeempool.

swift
// OperationQueue met pool van 3 threads
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility

// Operaties aanmaken
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) }
}

// Afhankelijkheid: operation2 wacht op operation1
operation2.addDependency(operation1)

// Toevoegen aan wachtrij
queue.addOperations([operation1, operation2], waitUntilFinished: false)

// Alle operaties annuleren
queue.cancelAllOperations()

GCD DispatchQueue als threadpool

DispatchQueue — de threadpool van Apple. Een gelijktijdige wachtrij (qos: .utility) gebruikt de systeem-threadpool, geoptimaliseerd voor de huidige belasting van het apparaat. Verschillende QoS (userInteractive, userInitiated, utility, background) worden toegewezen aan verschillende pools met verschillende prioriteiten. DispatchGroup maakt synchronisatie van meerdere taken mogelijk. DispatchWorkItem ondersteunt annulering en qualityOfService. Voor fijne controle maakt u uw eigen gelijktijdige wachtrijen via DispatchQueue(label: qos: attributes: .concurrent).

swift
// GCD DispatchQueue als threadpool
let customQueue = DispatchQueue(
    label: "com.app.background",
    qos: .utility,
    attributes: .concurrent,
    autoreleaseFrequency: .workItem
)

// Taken naar de pool sturen
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }

// DispatchGroup voor synchronisatie
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() // Beide taken voltooid
}

// Concurrency beperken via semaphore
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    pool.async {
        semaphore.wait()
        download(url)
        semaphore.signal()
    }
}

Configuratieparameters van Thread Pool

De configuratie van de threadpool heeft directe invloed op de prestaties van de app. Onjuiste parameters leiden tot onderbenutting van de CPU (te weinig threads) of overbelasting van het systeem (te veel threads). In mobiele apps verschillen de optimale waarden van serverwaarden vanwege beperkte resources en energieverbruik. Belangrijkste parameters: corePoolSize, maxPoolSize, queue capacity en keepAliveTime.

Berekening van de optimale poolgrootte

Formule voor IO-bound taken: corePoolSize = aantal CPU-kernen × 2 (threads wachten op invoer/uitvoer). Voor CPU-bound taken: corePoolSize = aantal CPU-kernen (threads zijn constant bezig met berekeningen). Op moderne mobiele apparaten (6-8 kernen) levert dit 6-8 threads voor CPU-bound en 12-16 voor IO-bound. Praktijktests tonen aan dat voor een typische mobiele app 3-4 threads optimaal zijn — meer threads verhogen het energieverbruik zonder prestatieverbetering.

Queue Capacity en gedrag bij overloop

De grootte van de takenwachtrij (work queue) bepaalt hoeveel taken kunnen wachten op uitvoering. Onbeperkte wachtrij (LinkedBlockingQueue zonder limiet) kan leiden tot OOM bij snelle taaktoestroom. Beperkte wachtrij (ArrayBlockingQueue met vaste grootte) weigert taken bij overloop. Voor mobiele apps wordt ArrayBlockingQueue met een capaciteit van 16-32 taken aanbevolen. CallerRunsPolicy — de beste RejectedExecutionHandler voor mobiele apparaten: vertraagt de afzender (pressure back) in plaats van de taak te verliezen.

ParameterAanbeveling voor mobielMotivering
corePoolSize2-4Beperkte resources van het mobiele apparaat
maxPoolSizecorePoolSize (of +1-2)Piekbelasting bij threadcreatie vermijden
keepAliveTime15-30 secondenSnel geheugen vrijmaken, maar zonder frequent maken
Queue capacity16-32Balans tussen buffering en OOM-risico
HandlerCallerRunsPolicyBackpressure zonder taakverlies

Veelvoorkomende fouten bij het werken met de threadpool

Mobiele app-ontwikkelaars maken vaak fouten bij het gebruik van de threadpool, wat leidt tot crashes, geheugenlekken en instabiele werking. Meest voorkomend: het niet aanroepen van shutdown() voor ExecutorService, het maken van een nieuwe pool voor elke bewerking, een te grote pool, deadlock tussen taken, gebruik van CachedThreadPool op Android.

Deadlock in Thread Pool

Deadlock treedt op wanneer een taak in de pool wacht op het resultaat van een andere taak uit dezelfde pool, maar alle threads bezet zijn met wachten. Voorbeeld: taak A stuurt taak B naar dezelfde pool en roept future.get() aan — als de pool uitgeput is, wacht taak A op taak B en kan taak B niet worden uitgevoerd omdat er geen vrije threads zijn. Oplossing: gebruik aparte pools voor verschillende taakniveaus of async callback in plaats van blokkerende .get().

kotlin
// Deadlock in Thread Pool
val pool = Executors.newFixedThreadPool(1)

// Taak A wacht op taak B — deadlock!
val futureA = pool.submit {
    // Deze taak wordt nooit uitgevoerd
    val futureB = pool.submit { 42 }
    futureB.get() // Voor altijd geblokkeerd
}

// Oplossing: aparte pools
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()

workerPool.submit {
    callbackPool.submit {
        // Wordt in aparte pool uitgevoerd — deadlock onmogelijk
    }
}

// Of gebruik CompletableFuture
workerPool.submit {
    CompletableFuture
        .supplyAsync { 42 }
        .thenAccept { result ->
            println(result)
        }
}

Onvoltooide pool en lekken

ExecutorService gemaakt in Activity moet worden beëindigd in onDestroy(). Als dit niet gebeurt, blijven threads in het geheugen hangen, zelfs na vernietiging van de Activity. Oplossing: bewaar de pool in Application scope of ViewModel, niet in Activity. Gebruik voor coroutines viewModelScope of lifecycleScope. Als de pool binnen Activity is gemaakt, roep dan zeker pool.shutdown() aan in onDestroy(). Gebruik voor testen es.shutdownNow() voor onmiddellijke stopzetting.

Veelgestelde vragen

Hoe verschilt Thread Pool van een gewone thread?

Thread Pool hergebruikt reeds gemaakte threads voor het uitvoeren van meerdere taken. Een gewone thread (raw Thread) wordt gemaakt, voert één taak uit en wordt vernietigd. Het maken van een thread kost ongeveer 1 MB geheugen en ~1 ms tijd. Thread Pool vermindert overhead, beperkt het maximale aantal threads en biedt een beheer-API (shutdown, awaitTermination).

Hoeveel threads moeten er in de pool voor een mobiele app?

Voor een typische mobiele app zijn 2-4 threads optimaal. Voor CPU-bound taken — het aantal CPU-kernen. Voor IO-bound taken — het aantal kernen × 2. Meer threads verhogen het energieverbruik en contextwisselingen zonder prestatieverbetering. Gebruik op Android Process.availableProcessors() om kernen te bepalen. Op iOS — ProcessInfo.processInfo.processorCount.

Wat is CachedThreadPool en waarom is het gevaarlijk op Android?

CachedThreadPool maakt threads indien nodig en hergebruikt bestaande threads. Probleem: het beperkt het maximale aantal threads niet. Als 100 taken tegelijk binnenkomen, worden 100 threads gemaakt. Dit leidt tot OOM op Android (elke thread ~1 MB). Gebruik newFixedThreadPool(n) met een expliciete limiet. CachedThreadPool is alleen toegestaan voor kortdurende burst-taken met garantie van een kleine omvang.

Moet shutdown() worden aangeroepen voor ExecutorService?

Ja, als de pool niet toebehoort aan een beheerde container (zoals coroutines). shutdown() stopt het accepteren van nieuwe taken en beëindigt threads na het voltooien van huidige taken. Zonder shutdown() blijven threads in het geheugen hangen en wordt de app niet afgesloten. Roep aan in onDestroy() voor Activity. Gebruik voor ViewModel coroutineScope. Het beëindigen van de pool is een verplicht onderdeel van resourcebeheer, analoog aan het sluiten van een Cursor of InputStream.

Zijn OperationQueue en DispatchQueue een threadpool?

Ja, OperationQueue en DispatchQueue zijn de threadpool die door iOS wordt geleverd. OperationQueue beperkt gelijktijdigheid via maxConcurrentOperationCount. DispatchQueue.global() gebruikt de systeem-threadpool zonder directe controle. In tegenstelling tot Java ThreadPoolExecutor beheert u geen corePoolSize of queue capacity — het systeem optimaliseert de pool automatisch onder de huidige belasting en het energieverbruik van het apparaat.

Samenvatting

  • Thread Pool — een pool van herbruikbare threads voor achtergrondtaken, die de overhead van threadcreatie vermindert.
  • Android gebruikt ThreadPoolExecutor en Executors.newFixedThreadPool(n) met expliciete poolgroottebeperking.
  • iOS biedt OperationQueue met maxConcurrentOperationCount en GCD DispatchQueue met QoS-pools.
  • Core pool size — minimaal aantal threads; maximum pool size — maximaal bij wachtrijoverloop.
  • Deadlock in de pool treedt op wanneer een taak wacht op een andere taak uit dezelfde pool.
  • CallerRunsPolicy heeft de voorkeur voor mobiele apps — vertraagt de afzender zonder taakverlies.
  • Optimale poolgrootte voor een typische mobiele app is 2-4 threads met afsluiting via shutdown().

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook