Parallelität und Multithreading in Kotlin: Fortgeschrittenes Niveau
\nIn der modernen Entwicklung mit Kotlin, insbesondere für Server- und Android-Anwendungen, ist ein effizientes Management der Parallelität der Schlüssel zu Leistung und Reaktionsfähigkeit. Auf fortgeschrittenem Niveau reicht es nicht aus, einfach eine Koroutine zu starten. Man muss verstehen, wie man den Zugriff auf Daten effektiv synchronisiert, Threads verwaltet und "höllische" Blockaden vermeidet. In diesem Artikel behandeln wir fortgeschrittene Techniken: von den Feinheiten von kotlinx.coroutines bis hin zu thread-sicheren Kollektionen und Channels.
1. Tiefer Einstieg in Koroutinen: Structured Concurrency und Scopes
\nDie Grundlage der fortgeschrittenen Arbeit ist die Structured Concurrency. Dies ist ein Prinzip, bei dem der Lebenszyklus einer Koroutine an einen Gültigkeitsbereich (Scope) gebunden ist. Wenn die übergeordnete Koroutine abgebrochen wird, werden alle ihre untergeordneten Aufgaben automatisch beendet. Dies verhindert Speicherlecks und unkontrollierte Prozesse.
\nAuf fortgeschrittenem Niveau sollten Sie eigene Scopes erstellen, anstatt sich auf den globalen GlobalScope zu verlassen.
import kotlinx.coroutines.*\nimport kotlin.coroutines.CoroutineContext\n\nclass DataRepository {\n // Eigener Scope mit Dispatcher und Exception-Handler\n private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { _, throwable ->\n println("Unbehandelter Fehler: ${throwable.message}")\n })\n\n fun loadData() {\n scope.launch {\n // untergeordnete Koroutinen\n val data = async { fetchFromNetwork() }\n val cache = async { loadFromCache() }\n process(data.await(), cache.await())\n }\n }\n\n fun cleanup() {\n scope.cancel() // bricht alle Koroutinen im Scope ab\n }\n}\n\nVerwenden Sie SupervisorJob, wenn der Fehler einer untergeordneten Koroutine die anderen nicht abbrechen soll. Dies ist entscheidend für UI-Ebenen oder parallele, unabhängige Anfragen.
2. Thread-sichere Kollektionen und atomare Operationen
\nAuch mit Koroutinen ist eine Synchronisation erforderlich, wenn Sie mit gemeinsamen, veränderlichen Daten arbeiten. Kotlin bietet mehrere Ansätze:
\n- \n
- Atomare Typen (
AtomicInteger,AtomicReference) — für einfache Zähler und Flags. \n - Thread-sichere Kollektionen (
ConcurrentHashMap,CopyOnWriteArrayList) — für komplexe Strukturen. \n - Mutex aus
kotlinx.coroutines— für kritische Abschnitte innerhalb von Koroutinen. \n
import kotlinx.coroutines.*\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.atomic.AtomicInteger\n\nclass SafeCounter {\n private val mutex = Mutex()\n private var counter = 0\n\n suspend fun increment() {\n mutex.withLock {\n counter++ // kritischer Abschnitt\n }\n }\n\n fun getCounter(): Int = counter\n}\n\nclass CacheManager {\n private val cache = ConcurrentHashMap<String, String>()\n private val hitCount = AtomicInteger(0)\n\n fun put(key: String, value: String) {\n cache[key] = value\n }\n\n fun get(key: String): String? {\n val result = cache[key]\n if (result != null) hitCount.incrementAndGet()\n return result\n }\n}\n\nWichtig: Mutex ist eine suspend-Funktion, blockiert also keinen Thread, sondern setzt die Koroutine aus. Dies ist effizienter als synchronized.
3. Channels und Flow: Reaktive Datenströme
\nVerwenden Sie zur Datenübertragung zwischen Koroutinen Channels (das Äquivalent zu BlockingQueue für Koroutinen) und Flow (ein kalter asynchroner Datenstrom).
Channels: Producer-Consumer
\nimport kotlinx.coroutines.channels.*\nimport kotlinx.coroutines.*\n\nfun Corout