Thread — den grundläggande enheten för processortid, med egen stack, som körs oberoende av andra trådar. Inom mobilutveckling används trådar för parallell exekvering av uppgifter så att gränssnittet förblir responsivt under långa operationer. Android stöder java.lang.Thread, Executors och Kotlin Coroutines, iOS — Thread (Objective-C), GCD och OperationQueue. Enligt Android Thread Documentation kräver skapandet av en nativ tråd att operativsystemet allokerar ~1 MB för stacken.
Nyckelpunkter
Thread (exekveringstråd) — en oberoende sekvens av instruktioner som operativsystemet kan schemalägga på en CPU-kärna. Varje process (app) innehåller minst en tråd — Main Thread. Extra trådar skapas för parallell exekvering av uppgifter. Varje tråd har en egen programstack (med lokala variabler), en programräknare (PC) och register. Heap-minnet är delat av alla trådar i processen.
I mobila operativsystem schemaläggs trådar med förebyggande multitasking (preemptive multitasking): operativsystemet kan när som helst avbryta en tråds exekvering och lämna över kontrollen till en annan (context switch). Kontextbyte är en dyr operation (1–10 mikrosekunder), eftersom det kräver att CPU-register sparas/återställs, TLB uppdateras och cacheminnen töms. Just därför försämrar ett alltför stort antal trådar (hundratals och tusentals) prestandan — operativsystemet lägger mer tid på växling än på exekvering.
Tråd och process är olika begrepp. En process är en instans av appen med dedikerat virtuellt minne. En tråd i processen delar detta minne med andra trådar. I Android fungerar varje komponent i appen (Activity, Service, BroadcastReceiver) i en process, men kan köras i olika trådar. En iOS-app är också en enda process med möjlighet att skapa extra trådar via GCD eller Thread.
Varje tråd i Java/Kotlin (Android) och NSThread (iOS) går igenom fem tillstånd: New (skapad), Runnable (redo att köras), Running (körs på CPU), Blocked/Waiting (väntar på en resurs eller notifiering), Terminated (avslutad). Övergångarna mellan tillstånden hanteras av operativsystemets schemaläggare och synkroniseringsprimitiver. Utvecklaren kan påverka trådens prioritet (Thread.setPriority()) och dess tillstånd (sleep, join, interrupt).
I Android går en tråd till tillståndet Blocked vid försök att ta en upptagen monitor (synchronized), vid anrop av Object.wait() eller Thread.sleep(). I iOS — vid anrop av NSCondition.wait(), pthread_cond_wait() eller dispatch_semaphore_wait(). I tillståndet Blocked förbrukar tråden inte CPU, men upptar minne (stack). En tråd kan avbrytas (interrupted) från en annan tråd, genom att få InterruptedException (Java) eller genom att kontrollera isCancelled (Kotlin Coroutines).
| Tillstånd | Beskrivning | Övergångsmetod |
|---|---|---|
| New | Tråden är skapad men inte startad | Thread() konstruktor |
| Runnable | Tråden är redo att köras, väntar på CPU | thread.start() |
| Running | Tråden körs på en CPU-kärna | Operativsystemets schemaläggare |
| Blocked/Waiting | Tråden väntar på en resurs, monitor eller notifiering | synchronized, wait(), sleep() |
| Terminated | Tråden har slutfört run() eller avbrutits | run() avslutad, interrupt() |
Context switch (kontextbyte) — operationen där operativsystemet sparar den aktuella trådens tillstånd (register, PC, TLB) och läser in ett annat tillstånd. I mobila system (Linux + ART, XNU för iOS) tar context switch 1–10 mikrosekunder. Om en tråd kör en uppgift på 100 mikrosekunder och context switch tar 5, går 5% av tiden förlorad. För att minimera context switch använder iOS GCD med work stealing, och Android pooler med fixedThreadCount.
Android har utvecklats från den lågnivåbaserade java.lang.Thread till moderna koroutiner. Varje abstraktionsnivå ger fler möjligheter med lägre overhead. Thread är basklassen, men direkt skapande rekommenderas inte: den nya tråden hanteras inte av en pool och är svår att övervaka och avbryta. AsyncTask (deprecated sedan API 30) var ett steg framåt, men led av minnesläckor och obekväm hantering av konfigurationer.
HandlerThread — en speciell underklass av Thread med Looper, som kan bearbeta en meddelandekö. Används för sekventiell exekvering av uppgifter på en bakgrundstråd, till exempel att skriva data till Room eller filer. HandlerThread skapas med ett anrop av start(), varefter man via Handler(handlerThread.looper) kan skicka meddelanden och Runnable. Anropet handlerThread.quit() stoppar Looper och avslutar tråden.
// Android: Thread, HandlerThread och Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. Direkt skapande av Thread (rekommenderas inte)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("Direkt tråd exekverad")
})
thread.start()
}
// 2. HandlerThread för sekventiella bakgrundsuppgifter
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Sekventiell exekvering på bakgrundstråden
Thread.sleep(500)
print("HandlerThread: uppgift utförd")
}
// Stoppa tråden (körs när uppgifterna är klara)
handlerThread.quitSafely()
}
// 3. Executors — trådpool
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 — modern standard
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
Exemplet ThreadExample visar alla fyra abstraktionsnivåer för trådar i Android. Direkt skapande av Thread är det mest lågnivåbaserade och ineffektiva tillvägagångssättet. HandlerThread är användbart för sekventiella uppgifter i bakgrunden. Executors.newFixedThreadPool(4) skapar en pool med 4 trådar för parallell exekvering av upp till 10 uppgifter. Kotlin Coroutines med Dispatchers.Default — en modern, effektiv och säker metod.
HandlerThread — en specialiserad underklass av Thread med inbyggd Looper och meddelandekö. Skapas med ett anrop av start(), varefter man via Handler(handlerThread.looper) kan skicka Runnable och meddelanden. HandlerThread kör uppgifter strikt sekventiellt — nästa uppgift startar inte förrän den föregående är klar. Detta är bekvämt vid skrivning av data till Room eller filer, där ordningen på operationer är kritisk. Anropet quitSafely() stoppar Looper efter att den aktuella uppgiften är klar.
iOS erbjuder också tre nivåer av arbete med trådar. Thread (Thread i Swift, NSThread i Objective-C) — ett lågnivå-API som direkt skapar en nativ tråd. GCD (Grand Central Dispatch) via DispatchQueue — det viktigaste verktyget för iOS-utvecklare, som automatiskt hanterar trådpoolen. OperationQueue — en högnivåabstraktion ovanpå GCD med stöd för beroenden, prioriteringar och avbrytning.
Direkt användning av Thread i modern iOS-utveckling är extremt sällsynt — GCD erbjuder alla nödvändiga möjligheter med automatisk hantering av minne och trådar. Thread används bara för specifika fall: inställning av thread-local storage (threadDictionary), skapande av en RunLoop för en bakgrundstråd eller integrering med C-bibliotek som väntar på pthread_t.
import Foundation
class ThreadManager {
// 1. Thread (låg nivå)
func createThread() {
let thread = Thread {
// Koden körs på en ny tråd
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// Parallell kö
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD async-uppgift")
}
// Barrier för synkronisering av skrivning
queue.async(flags: .barrier) {
// Exklusiv åtkomst under skrivning
print("Barrier write: exklusiv åtkomst")
}
}
// 3. OperationQueue med beroenden
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("Laddar ner...")
}
let process = BlockOperation {
print("Bearbetar...")
}
let save = BlockOperation {
print("Sparar...")
}
// Beroenden: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// Thread-safe samling 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)
}
}
}
Klassen ThreadSafeArray demonstrerar mönstret Concurrent Read / Exclusive Write genom GCD barrier. Läsning via queue.sync{} sker parallellt från flera trådar. Skrivning via queue.async(flags: .barrier) blockerar alla andra operationer (både läsning och skrivning) tills skrivningen är klar. Detta är effektivare än synchronized-block, eftersom det inte blockerar läsare så länge det inte finns någon skrivning.
Direkt användning av Thread i iOS är motiverat i tre fall: för thread-local storage (Thread.current.threadDictionary) — lagring av data som är bundna till tråden; för att skapa en speciell RunLoop på en bakgrundstråd med performSelector:onThread:; för integrering med C/C++-bibliotek som väntar på pthread_t. I alla andra fall är GCD via DispatchQueue att föredra — det hanterar automatiskt trådpoolen och energiförbrukningen.
Race condition (kapplöpningstillstånd) uppstår när två eller fler trådar samtidigt kommer åt delad data och minst en av dem skriver. Resultatet beror på exekveringsordningen (timing) och är oförutsägbart. För att förhindra race condition används synkroniseringsprimitiver. Inom mobilutveckling finns lås (synchronized, NSLock), atomära operationer (AtomicInteger, atomic-egenskaper i iOS) och köer (serial queue).
Valet av primitiv beror på scenariot. För enkla räknare och flaggor räcker atomära operationer (AtomicInteger, atomic property). För kritiska sektioner med flera operationer — lås (synchronized, NSLock). För komplexa datastrukturer — serial DispatchQueue eller GCD barrier. Lås är lättare att förstå, men är benägna att orsaka deadlock och livelock. Köer är mer komplexa, men säkrare.
// Synkronisering i Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — för enkla räknare
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — för kritiska sektioner
@Synchronized
fun synchronizedOperation() {
// Endast en tråd åt gången
doWork()
}
// 3. Mutex från koroutiner — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// protected code — trådsäker
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// Deadlock-exempel: A blockerar B, B blockerar 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 demonstrerar tre metoder för synkronisering. AtomicInteger.incrementAndGet() — en atomär operation utan lås (CAS). @Synchronized — Javas inbyggda monitor, låser hela objektet. Mutex.withLock — koroutinmutex, pausar koroutinen istället för att blockera tråden (effektivare). DeadlockExample visar en klassisk deadlock: två trådar tar låsen i olika ordning.
Thread Pool (trådpool) — en uppsättning förskapade trådar som återanvänds för att köra uppgifter. Istället för att skapa en ny tråd för varje uppgift (dyrt) tar poolen en ledig tråd från poolen. Om det inte finns lediga trådar hamnar uppgiften i en kö. Poolen hanterar automatiskt storleken: nya trådar skapas vid toppbelastning, inaktiva trådar avslutas. Detta minskar kostnaden för att skapa trådar tiotals gånger.
I Android skapar Executors.newFixedThreadPool(4) en pool med 4 trådar. Om det samtidigt kommer 10 uppgifter startar 4 direkt och 6 väntar i kön. Executors.newCachedThreadPool() skapar trådar efter behov (utan gräns) och avslutar inaktiva efter 60 sekunder. För iOS tillhandahåller GCD automatiskt pooler med globala köer, vars storlek motsvarar antalet CPU-kärnor och den aktuella belastningen.
I Kotlin Coroutines är trådpooler dolda inuti dispatchers. Dispatchers.Default använder en pool vars storlek är lika med antalet CPU-kärnor (minst 2). Dispatchers.IO — 64 trådar (tillräckligt för hundratals IO-bound-uppgifter, eftersom de flesta väntar på in- och utdata utan att uppta CPU). Varje dispatcher skalar automatiskt poolen efter belastning, vilket sparar batteri under inaktivitet.
Vanliga frågor
Thread — den grundläggande enheten för exekvering av kod i en app. Varje process kan ha många trådar som delar minne, men med en egen stack. Inom mobilutveckling används trådar för parallell exekvering av uppgifter utan att blockera UI. Android använder Thread, Executors, HandlerThread och Coroutines. iOS använder Thread, GCD (DispatchQueue) och OperationQueue.
Att skapa Thread kräver allokering av ~1 MB för stack i Android och ~512 KB i iOS — detta är en dyr operation. För 1000 uppgifter kräver direkt skapande av 1000 trådar ~1 GB enbart för stackar, plus overhead för context switch. Använd istället pooler (Executors, GCD) eller koroutiner — de återanvänder trådar och minskar overheaden tiotals gånger.
Race condition — oförutsägbart beteende när flera trådar samtidigt kommer åt delad data med skrivning. Den kan undvikas på tre sätt: använda atomära typer (AtomicInteger), lås (synchronized, NSLock) eller serialisera åtkomsten genom en kö (serial DispatchQueue, Actor i Kotlin). Bästa praxis — minimera delat mutable tillstånd och använda immutability.
Thread — ett nativt systemobjekt som upptar ~1 MB stack och är bundet till operativsystemets kärna. Koroutin — en lättviktig exekveringsenhet i Kotlin som inte är bunden till en specifik tråd och kan pausas (suspend) utan att blockera. En enda tråd kan köra tusentals koroutiner. Koroutiner är mer minneseffektiva och gör det möjligt att skriva asynkron kod utan callbacks.
Deadlock visar sig som att appen helt fryser utan ANR. I Android använder du Thread.getAllStackTraces() för att dumpa stackarna för alla trådar — två trådar väntar då på varandras lås. I iOS — Thread.callStackSymbols. Verktyg: Android Studio Profiler (fliken Threads), Instruments (iOS, Thread State View). Förebyggande: ta låsen i fast ordning, använd tryLock med timeout.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också