Parallelität und Multithreading in Kotlin: von A bis Z
Moderne Anwendungen erfordern zunehmend die gleichzeitige Ausführung mehrerer Aufgaben. In Kotlin, wie auch in anderen JVM-Sprachen, wird Multithreading traditionell über die Klasse Thread realisiert. Die Sprache bietet jedoch ein weitaus leistungsfähigeres und eleganteres Werkzeug – Koroutinen (coroutines). In diesem Artikel werden wir alle Aspekte der Parallelität und des Multithreadings in Kotlin behandeln: von den grundlegenden Konzepten bis hin zu fortgeschrittenen Techniken.
1. Grundlagen des Multithreadings: Thread und Runnable
Beginnen wir mit dem klassischen Ansatz. In Kotlin, wie auch in Java, kann man einen Thread direkt erstellen:
fun main() { val thread = Thread { for (i in 1..5) { println("Thread: $i") Thread.sleep(500) } } thread.start() println("Hauptthread beendet")}Die manuelle Thread-Verwaltung hat jedoch Nachteile:
- Hoher Overhead – Erstellen und Zerstören von Threads ist teuer.
- Komplexität der Synchronisation – leicht kann ein Race Condition auftreten.
- Begrenzte Anzahl von Threads – das System kann nur eine begrenzte Anzahl von Threads erstellen.
Für die Synchronisation des Zugriffs auf gemeinsame Daten wird das Schlüsselwort synchronized oder Sperren ReentrantLock verwendet:
class Counter { private var count = 0 @Synchronized fun increment() { count++ }}2. Koroutinen – der moderne Ansatz zur Parallelität
Koroutinen sind leichtgewichtige Threads, die die Probleme des klassischen Multithreadings lösen. Sie sind nicht an System-Threads gebunden und können auf einem einzigen Thread ausgeführt werden, indem sie zwischen Aufgaben umschalten.
2.1. Erste Koroutine
Um mit Koroutinen zu arbeiten, muss die Abhängigkeit org.jetbrains.kotlinx:kotlinx-coroutines-core eingebunden werden. Beispiel einer einfachen Koroutine:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) println("Welt!") } println("Hallo,")}Hier ist runBlocking die Brücke zwischen normalem Code und Koroutinen. launch startet eine neue Koroutine, und delay ist das Analogon zu Thread.sleep(), blockiert jedoch den Thread nicht.
2.2. Suspend-Funktionen
Das Schlüsselwort suspend ermöglicht es, die Ausführung einer Funktion anzuhalten, ohne den Thread zu blockieren:
suspend fun fetchData(): String { delay(2000L) // Simulation einer Netzwerkanfrage return "Daten empfangen"}
fun main() = runBlocking { val result = async { fetchData() } println("Warten...") println(result.await())}2.3. Strukturierte Nebenläufigkeit
Kotlin unterstützt strukturierte Nebenläufigkeit – alle untergeordneten Koroutinen werden automatisch abgeschlossen, bevor die übergeordnete Koroutine beendet wird:
fun main() = runBlocking { coroutineScope { launch { delay(1000L) println("Aufgabe 1") } launch { delay(500L) println("Aufgabe 2") } } println("Alle Aufgaben abgeschlossen")}3. Fortgeschrittene Techniken: Channel, Flow und Dispatchers
3.1. Channels
Channels ermöglichen den Datenaustausch zwischen Koroutinen. Sie sind das Analogon zu einer Warteschlange:
fun main() = runBlocking { val channel = Channel<Int>() launch { for (i in 1..5) { channel.send(i * i) } channel.close() } for (value in channel) { println(value) }}3.2. Flow – kalte Datenströme
Flow ist ein reaktiver Strom, der Werte auf Anfrage emittiert:
fun simpleFlow(): Flow<Int> = flow { for (i in 1..3) { delay(