Thread in mobiele ontwikkeling — wat is het, typen en beheer van threads

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

Thread — de basiseenheid van processortijd, met een eigen stack, die onafhankelijk van andere threads draait. In mobiele ontwikkeling worden threads gebruikt voor parallelle uitvoering van taken, zodat de interface responsief blijft tijdens langdurige bewerkingen. Android ondersteunt java.lang.Thread, Executors en Kotlin Coroutines, iOS — Thread (Objective-C), GCD en OperationQueue. Volgens Android Thread Documentation vereist het maken van een native thread dat het besturingssysteem ~1 MB voor de stack toewijst.

Belangrijkste punten

  • Thread — de kleinste eenheid van CPU-planning: elke thread is onafhankelijk en heeft een eigen stack
  • Thread aanmaken vereist ~1 MB voor de stack in Android en 512 KB in iOS, dus pools zijn efficiënter dan directe aanmaak
  • Android: Thread, Executors, HandlerThread, Coroutines — vier abstractieniveaus van threads
  • iOS: Thread (laag niveau), GCD (DispatchQueue), OperationQueue (hoog niveau)
  • Thread safety — gedeelde toegang tot mutable data vereist synchronisatie: locks, atomic, serial queues

Wat is Thread

Thread (uitvoeringsdraad) — een onafhankelijke reeks instructies die het besturingssysteem op een CPU-kern kan plannen. Elk proces (app) bevat ten minste één thread — Main Thread. Extra threads worden aangemaakt voor parallelle uitvoering van taken. Elke thread heeft een eigen programmastack (met lokale variabelen), een programmateller (PC) en registers. Het heap-geheugen wordt gedeeld door alle threads van het proces.

In mobiele besturingssystemen worden threads gepland via preëmptieve multitasking: het besturingssysteem kan de uitvoering van een thread op elk moment onderbreken en de controle overdragen aan een andere (context switch). Het wisselen van context is een dure bewerking (1–10 microseconden), omdat het CPU-registers moet opslaan/herstellen, de TLB moet bijwerken en caches moet legen. Precies daarom verslechtert een overmatig aantal threads (honderden en duizenden) de prestaties — het besturingssysteem besteedt meer tijd aan wisselen dan aan uitvoeren.

Thread en proces zijn verschillende begrippen. Een proces is een instantie van een app met toegewezen virtueel geheugen. Een thread binnen een proces deelt dat geheugen met andere threads. In Android werkt elk component van de app (Activity, Service, BroadcastReceiver) in één proces, maar kan in verschillende threads worden uitgevoerd. Een iOS-app is ook één proces met de mogelijkheid om extra threads te maken via GCD of Thread.

De levenscyclus van een thread: toestanden en overgangen

Elke thread in Java/Kotlin (Android) en NSThread (iOS) doorloopt vijf toestanden: New (aangemaakt), Runnable (klaar voor uitvoering), Running (draait op de CPU), Blocked/Waiting (wacht op een bron of melding), Terminated (beëindigd). De overgangen tussen toestanden worden beheerd door de scheduler van het besturingssysteem en synchronisatie-primitieven. De ontwikkelaar kan de prioriteit van de thread (Thread.setPriority()) en de toestand ervan (sleep, join, interrupt) beïnvloeden.

In Android gaat een thread naar de toestand Blocked bij een poging om een bezette monitor te veroveren (synchronized), bij een aanroep van Object.wait() of Thread.sleep(). In iOS — bij een aanroep van NSCondition.wait(), pthread_cond_wait() of dispatch_semaphore_wait(). In de toestand Blocked verbruikt de thread geen CPU, maar wel geheugen (stack). Een thread kan vanuit een andere thread worden onderbroken (interrupted), met een InterruptedException (Java) of door isCancelled te controleren (Kotlin Coroutines).

Toestand Beschrijving Overgangsmethode
New Thread is aangemaakt, maar niet gestart Thread() constructor
Runnable Thread is klaar voor uitvoering, wacht op CPU thread.start()
Running Thread draait op een CPU-kern Scheduler van het besturingssysteem
Blocked/Waiting Thread wacht op een bron, monitor of melding synchronized, wait(), sleep()
Terminated Thread heeft run() voltooid of is onderbroken run() voltooid, interrupt()

Context Switch en de kosten ervan

Context switch (contextwisseling) — de bewerking waarbij het besturingssysteem de toestand van de huidige thread (registers, PC, TLB) opslaat en de opgeslagen toestand van een andere laadt. In mobiele systemen (Linux + ART, XNU voor iOS) duurt een context switch 1–10 microseconden. Als een thread een taak in 100 microseconden uitvoert en een context switch 5 duurt, gaat 5% van de tijd verloren. Om context switches te minimaliseren gebruikt iOS GCD met work stealing, en Android pools met fixedThreadCount.

Thread in Android: van Thread tot Coroutines

Android is geëvolueerd van het laagniveau java.lang.Thread naar moderne coroutines. Elk abstractieniveau biedt meer mogelijkheden met lagere overhead. Thread is de basisklasse, maar directe aanmaak wordt niet aangeraden: de nieuwe thread wordt niet door een pool beheerd en is moeilijk te monitoren en te annuleren. AsyncTask (deprecated vanaf API 30) was een stap vooruit, maar leed aan geheugenlekken en onhandige verwerking van configuraties.

HandlerThread — een speciale subklasse van Thread met een Looper, die een berichtenwachtrij kan verwerken. Wordt gebruikt voor sequentiële uitvoering van taken op een achtergrondthread, bijvoorbeeld het wegschrijven van gegevens naar Room of bestanden. HandlerThread wordt aangemaakt met een aanroep van start(), waarna via Handler(handlerThread.looper) berichten en Runnable kunnen worden verzonden. De aanroep handlerThread.quit() stopt de Looper en beëindigt de thread.

kotlin
// Android: Thread, HandlerThread en Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors

class ThreadExample {

    // 1. Direct aanmaken van Thread (niet aanbevolen)
    fun directThread() {
        val thread = Thread(Runnable {
            Thread.sleep(1000)
            print("Directe thread uitgevoerd")
        })
        thread.start()
    }

    // 2. HandlerThread voor sequentiële achtergrondtaken
    fun handlerThreadExample() {
        val handlerThread = HandlerThread("BackgroundQueue")
        handlerThread.start()

        val handler = Handler(handlerThread.looper)
        handler.post {
            // Sequentiële uitvoering op de achtergrondthread
            Thread.sleep(500)
            print("HandlerThread: taak uitgevoerd")
        }

        // Thread stoppen (uitgevoerd wanneer de taken zijn voltooid)
        handlerThread.quitSafely()
    }

    // 3. Executors — threadpool
    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 — moderne standaard
    suspend fun coroutineExample() = kotlinx.coroutines.withContext(
        kotlinx.coroutines.Dispatchers.Default
    ) {
        print("Coroutine on thread: ${Thread.currentThread().getName()}")
    }
}

Het voorbeeld ThreadExample toont alle vier abstractieniveaus van threads in Android. Directe aanmaak van Thread is de laagstniveau- en inefficiëntste benadering. HandlerThread is nuttig voor sequentiële taken op de achtergrond. Executors.newFixedThreadPool(4) maakt een pool van 4 threads voor parallelle uitvoering van maximaal 10 taken. Kotlin Coroutines met Dispatchers.Default — een moderne, efficiënte en veilige methode.

HandlerThread: sequentiële achtergrondtaken

HandlerThread — een gespecialiseerde subklasse van Thread met ingebouwde Looper en berichtenwachtrij. Wordt aangemaakt met een aanroep van start(), waarna via Handler(handlerThread.looper) Runnable en berichten kunnen worden verzonden. HandlerThread voert taken strikt sequentieel uit — de volgende taak begint niet voordat de vorige is voltooid. Dit is handig bij het wegschrijven van gegevens naar Room of bestanden, waar de volgorde van bewerkingen cruciaal is. De aanroep quitSafely() stopt de Looper na voltooiing van de huidige taak.

Thread in iOS: Thread, GCD en OperationQueue

iOS biedt ook drie niveaus van werken met threads. Thread (Thread in Swift, NSThread in Objective-C) — een laagniveau-API die rechtstreeks een native thread maakt. GCD (Grand Central Dispatch) via DispatchQueue — het belangrijkste hulpmiddel voor iOS-ontwikkelaars, dat automatisch de threadpool beheert. OperationQueue — een hoogniveau-abstractie boven GCD met ondersteuning voor afhankelijkheden, prioriteiten en annulering.

Direct gebruik van Thread in moderne iOS-ontwikkeling komt uiterst zelden voor — GCD biedt alle benodigde mogelijkheden met automatisch geheugen- en threadbeheer. Thread wordt alleen gebruikt voor specifieke gevallen: instellen van thread-local storage (threadDictionary), aanmaken van een RunLoop voor een achtergrondthread of integratie met C-bibliotheken die pthread_t verwachten.

swift
import Foundation

class ThreadManager {

    // 1. Thread (laag niveau)
    func createThread() {
        let thread = Thread {
            // De code wordt op een nieuwe thread uitgevoerd
            print("Current thread: \(Thread.current)")
        }
        thread.name = "com.app.worker"
        thread.qualityOfService = .utility
        thread.start()
    }

    // 2. GCD — DispatchQueue
    func gcdExample() {
        // Parallelle wachtrij
        let queue = DispatchQueue(label: "com.app.concurrent",
                                 qos: .utility,
                                 attributes: .concurrent)

        queue.async {
            print("GCD async-taak")
        }

        // Barrier voor synchronisatie van schrijven
        queue.async(flags: .barrier) {
            // Exclusieve toegang tijdens het schrijven
            print("Barrier write: exclusieve toegang")
        }
    }

    // 3. OperationQueue met afhankelijkheden
    func operationQueueExample() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2
        queue.qualityOfService = .background

        let download = BlockOperation {
            print("Bezig met downloaden...")
        }
        let process = BlockOperation {
            print("Bezig met verwerken...")
        }
        let save = BlockOperation {
            print("Bezig met opslaan...")
        }

        // Afhankelijkheden: download -> process -> save
        process.addDependency(download)
        save.addDependency(process)

        queue.addOperations([download, process, save], waitUntilFinished: false)
    }
}

// Thread-safe collectie 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)
        }
    }
}

De klasse ThreadSafeArray demonstreert het patroon Concurrent Read / Exclusive Write via een GCD-barrier. Lezen via queue.sync{} gebeurt parallel vanuit meerdere threads. Schrijven via queue.async(flags: .barrier) blokkeert alle andere bewerkingen (zowel lezen als schrijven) totdat het schrijven is voltooid. Dit is efficiënter dan synchronized-blokken, omdat lezers niet worden geblokkeerd zolang er geen schrijfactie is.

iOS Thread vs GCD: wanneer Thread direct gebruiken

Direct gebruik van Thread in iOS is gerechtvaardigd in drie gevallen: voor thread-local storage (Thread.current.threadDictionary) — het opslaan van gegevens die aan de thread zijn gebonden; voor het aanmaken van een speciale RunLoop op een achtergrondthread met performSelector:onThread:; voor integratie met C/C++-bibliotheken die pthread_t verwachten. In alle andere gevallen heeft GCD via DispatchQueue de voorkeur — het beheert automatisch de threadpool en het energieverbruik.

Threadsynchronisatie: locks, atomic, serial queues

Race condition (raceconditie) ontstaat wanneer twee of meer threads tegelijkertijd toegang hebben tot gedeelde gegevens en ten minste één van hen schrijft. Het resultaat hangt af van de uitvoeringsvolgorde (timing) en is onvoorspelbaar. Om een race condition te voorkomen worden synchronisatie-primitieven gebruikt. In mobiele ontwikkeling zijn vergrendelingen (synchronized, NSLock), atomaire bewerkingen (AtomicInteger, atomic-eigenschappen in iOS) en wachtrijen (serial queue) beschikbaar.

De keuze van het primitief hangt af van het scenario. Voor eenvoudige tellers en vlaggen zijn atomaire bewerkingen (AtomicInteger, atomic property) voldoende. Voor kritieke secties met meerdere bewerkingen — vergrendelingen (synchronized, NSLock). Voor complexe datastructuren — serial DispatchQueue of GCD-barrier. Vergrendelingen zijn gemakkelijker te begrijpen, maar zijn gevoelig voor deadlock en livelock. Wachtrijen zijn complexer, maar veiliger.

kotlin
// Synchronisatie in Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class Counter {

    // 1. AtomicInteger — voor eenvoudige tellers
    private val atomicCount = AtomicInteger(0)
    fun incrementAtomic() = atomicCount.incrementAndGet()

    // 2. synchronized — voor kritieke secties
    @Synchronized
    fun synchronizedOperation() {
        // Slechts één thread tegelijk
        doWork()
    }

    // 3. Mutex uit coroutines — suspend-safe
    private val mutex = Mutex()
    suspend fun mutexOperation() {
        mutex.withLock {
            // protected code — thread-safe
            doWork()
        }
    }

    private fun doWork() { /* critical section */ }
}

// Deadlock-voorbeeld: A vergrendelt B, B vergrendelt 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 demonstreert drie benaderingen van synchronisatie. AtomicInteger.incrementAndGet() — een atomaire bewerking zonder vergrendelingen (CAS). @Synchronized — de ingebouwde monitor van Java, vergrendelt het hele object. Mutex.withLock — een coroutine-mutex, schort de coroutine op in plaats van de thread te blokkeren (efficiënter). DeadlockExample toont een klassieke deadlock: twee threads veroveren vergrendelingen in verschillende volgorde.

Thread Pools: waarom Executors beter is dan Thread

Thread Pool (threadpool) — een set van vooraf aangemaakte threads die worden hergebruikt voor het uitvoeren van taken. In plaats van voor elke taak een nieuwe thread te maken (duur), neemt de pool een vrije thread uit de pool. Als er geen vrije threads zijn, wordt de taak in een wachtrij geplaatst. De pool beheert automatisch de grootte: nieuwe threads worden aangemaakt bij piekbelasting, inactieve threads worden beëindigd. Dit vermindert de overhead van het aanmaken van threads tientallen keren.

In Android maakt Executors.newFixedThreadPool(4) een pool van 4 threads. Als er tegelijkertijd 10 taken binnenkomen, starten er 4 direct en wachten er 6 in de wachtrij. Executors.newCachedThreadPool() maakt threads naar behoefte (zonder limiet) en beëindigt inactieve threads na 60 seconden. Voor iOS biedt GCD automatisch pools van globale wachtrijen, waarvan de omvang overeenkomt met het aantal CPU-kernen en de huidige belasting.

In Kotlin Coroutines zijn threadpools verborgen in dispatchers. Dispatchers.Default gebruikt een pool ter grootte van het aantal CPU-kernen (minimaal 2). Dispatchers.IO — 64 threads (genoeg voor honderden IO-bound taken, omdat de meeste op invoer-uitvoer wachten zonder CPU te bezetten). Elke dispatcher schaalt de pool automatisch naar de belasting, waardoor batterij-energie wordt bespaard tijdens inactiviteit.

Veelgestelde vragen

Wat is Thread in mobiele ontwikkeling?

Thread — de basiseenheid van uitvoering van code in een app. Elk proces kan veel threads hebben die het geheugen delen, maar een eigen stack hebben. In mobiele ontwikkeling worden threads gebruikt voor parallelle uitvoering van taken zonder de UI te blokkeren. Android gebruikt Thread, Executors, HandlerThread en Coroutines. iOS gebruikt Thread, GCD (DispatchQueue) en OperationQueue.

Waarom wordt direct aanmaken van Thread niet aangeraden?

Thread aanmaken vereist het toewijzen van ~1 MB voor de stack in Android en ~512 KB in iOS — dit is een dure bewerking. Voor 1000 taken vereist het direct aanmaken van 1000 threads ~1 GB alleen voor de stacks, plus overhead voor context switches. Gebruik in plaats van Thread pools (Executors, GCD) of coroutines — zij hergebruiken threads en verminderen de overhead tientallen keren.

Wat is een race condition en hoe voorkom je die?

Race condition — onvoorspelbaar gedrag bij gelijktijdige toegang van meerdere threads tot gedeelde gegevens met schrijven. Het kan op drie manieren worden voorkomen: gebruik van atomaire typen (AtomicInteger), vergrendelingen (synchronized, NSLock) of serialisatie van toegang via een wachtrij (serial DispatchQueue, Actor in Kotlin). Beste praktijk — minimaliseer gedeelde mutable toestand en gebruik immutability.

Waarin verschilt Thread van een coroutine?

Thread — een native systeemobject dat ~1 MB stack in beslag neemt en is gebonden aan de kernel van het besturingssysteem. Coroutine — een lichtgewicht uitvoeringseenheid in Kotlin die niet aan een specifieke thread is gebonden en kan worden opgeschort (suspend) zonder te blokkeren. Eén thread kan duizenden coroutines uitvoeren. Coroutines zijn efficiënter qua geheugen en maken het schrijven van asynchrone code zonder callbacks mogelijk.

Hoe vang je een deadlock op in een mobiele app?

Deadlock manifesteert zich als het volledig vastlopen van de app zonder ANR. Gebruik in Android Thread.getAllStackTraces() om de stacks van alle threads te dumpen — twee threads wachten dan op elkaars vergrendelingen. In iOS — Thread.callStackSymbols. Hulpmiddelen: Android Studio Profiler (tabblad Threads), Instruments (iOS, Thread State View). Preventie: verover vergrendelingen in een vaste volgorde, gebruik tryLock met een timeout.

Conclusies

  • Thread — de kleinste eenheid van CPU: onafhankelijke uitvoering met eigen stack, heap-geheugen is gedeeld
  • Vijf toestanden van een thread: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android is geëvolueerd van Thread → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS biedt Thread, GCD (DispatchQueue) en OperationQueue — van laag naar hoog niveau
  • Race condition wordt opgelost met vergrendelingen (synchronized, NSLock), atomaire typen en serial-wachtrijen
  • Deadlock ontstaat bij het kruislings veroveren van vergrendelingen — te voorkomen door een vaste volgorde
  • Thread Pool is efficiënter dan het aanmaken van nieuwe Threads: hergebruikt threads, vermindert de overhead van context switches

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