Parallelität und Multithreading in Java: Von den Grundlagen bis zu fortgeschrittenen Techniken
Moderne Anwendungen erfordern maximale Leistung. In Java sind Parallelität und Multithreading die wichtigsten Werkzeuge für die effiziente Nutzung von Mehrkernprozessoren. In diesem Artikel werden wir uns ansehen, wie man richtig mit Threads, Synchronisation und modernen Parallelitätsutilities arbeitet.
Was ist Multithreading in Java?
Multithreading ist die Fähigkeit eines Programms, mehrere Aufgaben gleichzeitig auszuführen. In Java ist jeder Thread ein leichtgewichtiger Prozess mit einem eigenen Aufrufstapel. Die Hauptvorteile sind:
- Leistungssteigerung durch parallele Ausführung
- Reaktionsfähigere Benutzeroberfläche
- Effiziente Nutzung der CPU-Ressourcen
Thread-Erstellung: Zwei Ansätze
In Java gibt es zwei Hauptmethoden, um einen Thread zu erstellen:
1. Vererbung von der Klasse Thread
public class MyThread extends Thread { @Override public void run() { System.out.println("Thread wird ausgeführt: " + Thread.currentThread().getName()); }
public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Thread starten }}2. Implementierung des Runnable-Interfaces
public class Task implements Runnable { @Override public void run() { System.out.println("Aufgabe wird im Thread ausgeführt: " + Thread.currentThread().getName()); }
public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.start(); }}Es wird empfohlen, Runnable zu verwenden, da es die Möglichkeit der Vererbung anderer Klassen nicht einschränkt.
Thread-Synchronisation: Race Conditions vermeiden
Wenn mehrere Threads auf gemeinsame Daten zugreifen, treten Synchronisationsprobleme auf. Betrachten wir ein klassisches Beispiel – einen Zähler:
public class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }}Das Schlüsselwort synchronized garantiert, dass nur ein Thread die Methode gleichzeitig ausführen kann. Eine Alternative ist die Verwendung von Lock aus dem Paket java.util.concurrent.locks.
Deadlock: Wie man gegenseitige Blockaden vermeidet
Ein Deadlock tritt auf, wenn zwei Threads auf die Freigabe der Ressourcen des jeweils anderen warten. Beispiel:
public class DeadlockExample { private final Object lock1 = new Object(); private final Object lock2 = new Object();
public void method1() { synchronized (lock1) { synchronized (lock2) { // Kritischer Abschnitt } } }
public void method2() { synchronized (lock2) { synchronized (lock1) { // Kritischer Abschnitt } } }}Um einen Deadlock zu vermeiden, sollten Sie Sperren immer in derselben Reihenfolge anfordern oder tryLock() mit einem Timeout verwenden.
ExecutorService: Verwaltung eines Thread-Pools
Das manuelle Erstellen von Threads ist ineffizient. Java bietet ExecutorService zur Verwaltung eines Thread-Pools:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) { executor.submit(() -> { System.out.println("Wird ausgeführt in: " + Thread.currentThread().getName()); }); }
executor.shutdown(); // Beendigung nach Ausführung aller Aufgaben }}Die wichtigsten