Thread — die grundlegende Einheit der Prozessorzeit, die einen eigenen Stack besitzt und unabhängig von anderen Threads ausgeführt wird. In der mobilen Entwicklung werden Threads für die parallele Ausführung von Aufgaben verwendet, damit die Benutzeroberfläche während langer Operationen reaktionsfähig bleibt. Android unterstützt java.lang.Thread, Executors und Kotlin Coroutines, iOS — Thread (Objective-C), GCD und OperationQueue. Laut Android Thread Documentation erfordert die Erstellung eines nativen Threads die Zuweisung von ~1 MB für den Stack durch das Betriebssystem.
Wichtige Punkte
Thread (Ausführungsfaden) — eine unabhängige Sequenz von Anweisungen, die das Betriebssystem auf einem CPU-Kern planen kann. Jeder Prozess (Anwendung) enthält mindestens einen Thread — den Main Thread. Zusätzliche Threads werden für die parallele Ausführung von Aufgaben erstellt. Jeder Thread hat seinen eigenen Programmstack (mit lokalen Variablen), einen Befehlszähler (PC) und Register. Der Heap-Speicher wird von allen Threads des Prozesses gemeinsam genutzt.
In mobilen Betriebssystemen werden Threads durch präemptives Multitasking geplant: Das BS kann die Ausführung eines Threads jederzeit unterbrechen und die Kontrolle an einen anderen übergeben (Context Switch). Der Kontextwechsel ist eine teure Operation (1-10 Mikrosekunden), da er das Speichern/Wiederherstellen von CPU-Registern, das Aktualisieren des TLB und das Leeren von Caches erfordert. Aus diesem Grund verschlechtert eine übermäßige Anzahl von Threads (Hunderte und Tausende) die Leistung — das BS verbringt mehr Zeit mit dem Wechseln als mit dem Ausführen.
Thread und Prozess — unterschiedliche Konzepte. Ein Prozess ist eine Instanz einer Anwendung mit dediziertem virtuellem Speicher. Ein Thread innerhalb eines Prozesses teilt diesen Speicher mit anderen Threads. In Android arbeitet jede Komponente der Anwendung (Activity, Service, BroadcastReceiver) in einem Prozess, kann aber in verschiedenen Threads ausgeführt werden. Eine iOS-Anwendung ist ebenfalls ein einzelner Prozess mit der Möglichkeit, über GCD oder Thread zusätzliche Threads zu erstellen.
Jeder Thread in Java/Kotlin (Android) und NSThread (iOS) durchläuft fünf Zustände: New (erstellt), Runnable (bereit zur Ausführung), Running (auf CPU ausführend), Blocked/Waiting (wartet auf Ressource oder Benachrichtigung), Terminated (beendet). Die Übergänge zwischen den Zuständen werden vom BS-Planer und Synchronisationsprimitive verwaltet. Der Entwickler kann die Priorität des Threads (Thread.setPriority()) und seinen Zustand (sleep, join, interrupt) beeinflussen.
In Android wechselt ein Thread in den Zustand Blocked, wenn er versucht, einen belegten Monitor (synchronized) zu erfassen, Object.wait() oder Thread.sleep() aufruft. In iOS — beim Aufruf von NSCondition.wait(), pthread_cond_wait() oder dispatch_semaphore_wait(). Im Zustand Blocked verbraucht der Thread keine CPU, belegt aber Speicher (Stack). Ein Thread kann von einem anderen Thread unterbrochen (interrupted) werden und erhält eine InterruptedException (Java) oder isCancelled (Kotlin Coroutines).
| Zustand | Beschreibung | Übergangsmethode |
|---|---|---|
| New | Thread erstellt, aber nicht gestartet | Thread()-Konstruktor |
| Runnable | Thread bereit zur Ausführung, wartet auf CPU | thread.start() |
| Running | Thread wird auf CPU-Kern ausgeführt | BS-Planer |
| Blocked/Waiting | Thread wartet auf Ressource, Monitor oder Benachrichtigung | synchronized, wait(), sleep() |
| Terminated | Thread hat run() beendet oder wurde unterbrochen | run() abgeschlossen, interrupt() |
Context Switch (Kontextwechsel) — eine Operation, bei der das BS den Zustand des aktuellen Threads (Register, PC, TLB) speichert und den gespeicherten Zustand eines anderen lädt. In mobilen Systemen (Linux + ART, XNU für iOS) dauert ein Context Switch 1-10 Mikrosekunden. Wenn ein Thread eine Aufgabe in 100 Mikrosekunden ausführt und der Context Switch 5 dauert, werden 5% der Zeit verschwendet. Zur Minimierung des Context Switch verwendet iOS GCD mit Work Stealing, Android — Pools mit fixedThreadCount.
Android hat sich von java.lang.Thread auf niedriger Ebene zu modernen Coroutines entwickelt. Jede Abstraktionsebene bietet mehr Möglichkeiten bei geringerem Overhead. Thread ist die Basisklasse, aber seine direkte Erstellung wird nicht empfohlen: Der neue Thread wird nicht von einem Pool verwaltet, ist schwer zu überwachen und abzubrechen. AsyncTask (seit API 30 veraltet) war ein Schritt nach vorne, litt aber unter Speicherlecks und unbequemer Konfigurationsbehandlung.
HandlerThread — eine spezielle Unterklasse von Thread mit Looper, die eine Nachrichtenwarteschlange verarbeiten kann. Es wird für die sequenzielle Ausführung von Aufgaben in einem Hintergrundthread verwendet, z.B. zum Schreiben von Daten in Room oder Dateien. HandlerThread wird durch Aufruf von start() erstellt, danach können über Handler(handlerThread.looper) Nachrichten und Runnable gesendet werden. Der Aufruf handlerThread.quit() stoppt den Looper und beendet den Thread.
// Android: Thread, HandlerThread und Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. Direkte Thread-Erstellung (nicht empfohlen)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("Direct thread executed")
})
thread.start()
}
// 2. HandlerThread für sequenzielle Hintergrundaufgaben
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Sequentielle Ausführung im Hintergrundthread
Thread.sleep(500)
print("HandlerThread: Aufgabe abgeschlossen")
}
// Thread stoppen (wird ausgeführt, wenn Aufgaben abgeschlossen sind)
handlerThread.quitSafely()
}
// 3. Executors — Thread-Pool
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 — moderner Standard
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
Das Beispiel ThreadExample zeigt alle vier Abstraktionsebenen von Threads in Android. Die direkte Erstellung von Thread ist der niedrigste und ineffizienteste Ansatz. HandlerThread ist nützlich für sequenzielle Aufgaben im Hintergrund. Executors.newFixedThreadPool(4) erstellt einen Pool von 4 Threads für die parallele Ausführung von bis zu 10 Aufgaben. Kotlin Coroutines mit Dispatchers.Default — die moderne, effiziente und sichere Methode.
HandlerThread — eine spezialisierte Unterklasse von Thread mit integriertem Looper und Nachrichtenwarteschlange. Es wird durch Aufruf von start() erstellt, danach können über Handler(handlerThread.looper) Runnable und Nachrichten gesendet werden. HandlerThread führt Aufgaben streng sequenziell aus — die nächste Aufgabe beginnt erst nach Abschluss der vorherigen. Dies ist praktisch zum Schreiben von Daten in Room oder Dateien, wo die Reihenfolge der Operationen kritisch ist. Der Aufruf quitSafely() stoppt den Looper nach Abschluss der aktuellen Aufgabe.
iOS bietet ebenfalls drei Ebenen der Arbeit mit Threads. Thread (Thread in Swift, NSThread in Objective-C) — eine Low-Level-API, die direkt einen nativen Thread erstellt. GCD (Grand Central Dispatch) über DispatchQueue — das Hauptwerkzeug für iOS-Entwickler, das automatisch den Thread-Pool verwaltet. OperationQueue — eine High-Level-Abstraktion über GCD mit Unterstützung für Abhängigkeiten, Prioritäten und Abbruch.
Die direkte Verwendung von Thread in der modernen iOS-Entwicklung ist äußerst selten — GCD bietet alle notwendigen Fähigkeiten mit automatischer Speicher- und Thread-Verwaltung. Thread wird nur für spezifische Fälle verwendet: Einrichten von Thread-Local-Speicher (threadDictionary), Erstellen eines RunLoop für Hintergrundthreads oder Integration mit C-Bibliotheken, die pthread_t erwarten.
import Foundation
class ThreadManager {
// 1. Thread (niedrige Ebene)
func createThread() {
let thread = Thread {
// Code wird in neuem Thread ausgeführt
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// Parallele Warteschlange
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("GCD async task")
}
// Barrier zur Schreibsynchronisation
queue.async(flags: .barrier) {
// Exklusiver Zugriff während des Schreibens
print("Barrier write: exclusive access")
}
}
// 3. OperationQueue mit Abhängigkeiten
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("Downloading...")
}
let process = BlockOperation {
print("Processing...")
}
let save = BlockOperation {
print("Saving...")
}
// Abhängigkeiten: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// Thread-sichere Sammlung über 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)
}
}
}
Die Klasse ThreadSafeArray demonstriert das Muster Concurrent Read / Exclusive Write über GCD Barrier. Das Lesen über queue.sync{} wird parallel von mehreren Threads ausgeführt. Das Schreiben über queue.async(flags: .barrier) blockiert alle anderen Operationen (sowohl Lesen als auch Schreiben) bis zum Abschluss des Schreibvorgangs. Dies ist effizienter als synchronized-Blöcke, da es Leser nicht blockiert, solange keine Schreiboperation stattfindet.
Die direkte Verwendung von Thread in iOS ist in drei Fällen gerechtfertigt: für Thread-Local-Speicher (Thread.current.threadDictionary) — Speicherung von an den Thread gebundenen Daten; zum Erstellen eines speziellen RunLoop auf einem Hintergrundthread mit performSelector:onThread:; für die Integration mit C/C++-Bibliotheken, die pthread_t erwarten. In allen anderen Fällen ist GCD über DispatchQueue vorzuziehen — es verwaltet automatisch den Thread-Pool und den Energieverbrauch.
Race Condition (Wettlaufsituation) tritt auf, wenn zwei oder mehr Threads gleichzeitig auf gemeinsame Daten zugreifen und mindestens einer der Threads schreibt. Das Ergebnis hängt von der Ausführungsreihenfolge (Timing) ab und ist unvorhersehbar. Zur Vermeidung von Race Conditions werden Synchronisationsprimitive verwendet. In der mobilen Entwicklung sind Sperren (synchronized, NSLock), atomare Operationen (AtomicInteger, atomic-Eigenschaften von iOS) und Warteschlangen (Serial Queue) verfügbar.
Die Wahl des Primitivs hängt vom Szenario ab. Für einfache Zähler und Flags reichen atomare Operationen (AtomicInteger, atomic property). Für kritische Abschnitte mit mehreren Operationen — Sperren (synchronized, NSLock). Für komplexe Datenstrukturen — Serial DispatchQueue oder GCD Barrier. Sperren sind leichter zu verstehen, aber anfällig für Deadlocks und Livelocks. Warteschlangen sind komplexer, aber sicherer.
// Synchronisation in Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — für einfache Zähler
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — für kritische Abschnitte
@Synchronized
fun synchronizedOperation() {
// Nur ein Thread gleichzeitig
doWork()
}
// 3. Mutex aus Coroutines — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// Geschützter Code — threadsicher
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// Deadlock-Beispiel: A sperrt B, B sperrt 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 demonstriert drei Ansätze zur Synchronisation. AtomicInteger.incrementAndGet() — atomare Operation ohne Sperren (CAS). @Synchronized — eingebauter Java-Monitor, sperrt das gesamte Objekt. Mutex.withLock — Coroutine-Mutex, pausiert die Coroutine anstatt den Thread zu blockieren (effizienter). DeadlockExample zeigt einen klassischen Deadlock: Zwei Threads erfassen Sperren in unterschiedlicher Reihenfolge.
Thread Pool — eine Menge von vorab erstellten Threads, die zur Ausführung von Aufgaben wiederverwendet werden. Anstatt für jede Aufgabe einen neuen Thread zu erstellen (teuer), nimmt der Pool einen freien Thread aus dem Pool. Wenn keine freien Threads vorhanden sind, wird die Aufgabe in eine Warteschlange gestellt. Der Pool verwaltet die Größe automatisch: Bei Spitzenlast werden neue Threads erstellt, inaktive Threads werden beendet. Dies reduziert den Overhead der Thread-Erstellung um ein Vielfaches.
In Android erstellt Executors.newFixedThreadPool(4) einen Pool von 4 Threads. Wenn gleichzeitig 10 Aufgaben eintreffen, beginnen 4 sofort mit der Ausführung, 6 warten in der Warteschlange. Executors.newCachedThreadPool() erstellt Threads nach Bedarf (ohne Limit) und beendet inaktive Threads nach 60 Sekunden. Für iOS stellt GCD automatisch Pools von globalen Warteschlangen bereit, deren Größe der Anzahl der CPU-Kerne und der aktuellen Last entspricht.
In Kotlin Coroutines sind die Thread-Pools innerhalb der Dispatcher verborgen. Dispatchers.Default verwendet einen Pool in der Größe der Anzahl der CPU-Kerne (mindestens 2). Dispatchers.IO — 64 Threads (ausreichend für hunderte IO-gebundene Aufgaben, da die meisten auf Ein-/Ausgabe warten und keine CPU belegen). Jeder Dispatcher skaliert den Pool automatisch entsprechend der Last und spart so im Leerlauf Batteriestrom.
Häufig gestellte Fragen
Thread — die grundlegende Einheit der Codeausführung in einer Anwendung. Jeder Prozess kann mehrere Threads haben, die Speicher gemeinsam nutzen, aber einen eigenen Stack besitzen. In der mobilen Entwicklung werden Threads für die parallele Ausführung von Aufgaben verwendet, ohne die UI zu blockieren. Android verwendet Thread, Executors, HandlerThread und Coroutines. iOS verwendet Thread, GCD (DispatchQueue) und OperationQueue.
Thread-Erstellung erfordert die Zuweisung von ~1 MB Stack unter Android und ~512 KB unter iOS — dies ist eine teure Operation. Für 1000 Aufgaben würde die direkte Erstellung von 1000 Threads ~1 GB nur für die Stacks benötigen, plus den Overhead des Context Switch. Verwenden Sie anstelle von Thread Pools (Executors, GCD) oder Coroutines — sie verwenden Threads wieder und reduzieren den Overhead um ein Vielfaches.
Race Condition — unvorhersehbares Verhalten beim gleichzeitigen Zugriff mehrerer Threads auf gemeinsame Daten mit Schreibzugriff. Drei Wege zur Vermeidung: Verwendung atomarer Typen (AtomicInteger), Sperren (synchronized, NSLock) oder Serialisierung des Zugriffs über eine Warteschlange (DispatchQueue serial, Actor in Kotlin). Die beste Praxis ist, gemeinsamen mutable-Zustand zu minimieren und Immutability zu verwenden.
Thread — ein nativen Systemobjekt, das ~1 MB Stack belegt und an einen BS-Kern gebunden ist. Coroutine — eine leichtgewichtige Ausführungseinheit in Kotlin, die nicht an einen bestimmten Thread gebunden ist und ohne Blockierung pausieren (suspend) kann. Ein einzelner Thread kann tausende Coroutines ausführen. Coroutines sind speichereffizienter und ermöglichen asynchronen Code ohne Callbacks.
Deadlock zeigt sich als vollständiges Einfrieren der Anwendung ohne ANR. In Android verwenden Sie Thread.getAllStackTraces() für einen Dump aller Thread-Stacks — zwei Threads warten auf gegenseitige Sperren. In iOS — Thread.callStackSymbols. Werkzeuge: Android Studio Profiler (Threads-Tab), Instruments (iOS, Thread State View). Vorbeugung: Sperren in festgelegter Reihenfolge erfassen, tryLock mit Timeout verwenden.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch