Parallelität und Multithreading in Java: Fortgeschrittenes Niveau
\n\nMultithreading in Java ist eines der mächtigsten und gleichzeitig komplexesten Themen. Auf der Anfängerebene verwenden wir synchronized und Thread, aber für den Aufbau leistungsfähiger und skalierbarer Systeme ist ein tieferes Verständnis erforderlich. In diesem Artikel behandeln wir fortgeschrittene Parallelitätsmechanismen: Thread-Pools, ForkJoinPool, asynchrone Programmierung mit CompletableFuture, Feineinstellung von Sperren und Speicherverwaltung. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen des Multithreading vertraut sind.
1. Fortgeschrittene Arbeit mit Thread-Pools: ExecutorService und Konfiguration
\n\nDie Verwendung von ExecutorService ist der Standard in der industriellen Entwicklung. Der Standard-Executors.newFixedThreadPool(10) ist jedoch nicht immer geeignet. Betrachten wir die Feineinstellung von ThreadPoolExecutor.
import java.util.concurrent.*;\n\npublic class CustomThreadPool {\n public static void main(String[] args) {\n int corePoolSize = 4;\n int maxPoolSize = 8;\n long keepAliveTime = 30;\n TimeUnit unit = TimeUnit.SECONDS;\n BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);\n\n ThreadPoolExecutor executor = new ThreadPoolExecutor(\n corePoolSize,\n maxPoolSize,\n keepAliveTime,\n unit,\n workQueue,\n new ThreadPoolExecutor.CallerRunsPolicy() // Sättigungsstrategie\n );\n\n // Überwachung des Pool-Zustands\n executor.submit(() -> {\n System.out.println("Aktive Threads: " + executor.getActiveCount());\n System.out.println("Abgeschlossene Aufgaben: " + executor.getCompletedTaskCount());\n });\n\n executor.shutdown();\n }\n}\n\nWichtige Parameter:
\n- \n
corePoolSize— minimale Anzahl von Threads, die auch ohne Aufgaben gehalten werden. \n maxPoolSize— maximale Anzahl von Threads. \n keepAliveTime— Lebensdauer überschüssiger Threads. \n workQueue— Aufgabenwarteschlange.ArrayBlockingQueuemit begrenzter Größe verhindert Speicherüberlauf. \n - Sättigungsstrategie — was zu tun ist, wenn die Warteschlange voll ist und alle Threads beschäftigt sind.
CallerRunsPolicyverlangsamt den aufrufenden Thread. \n
Für langlaufende I/O-Aufgaben ist es besser, corePoolSize zu erhöhen, für CPU-gebundene Aufgaben sollte man sich an Runtime.getRuntime().availableProcessors() orientieren.
2. ForkJoinPool und rekursive Aufgaben
\n\nForkJoinPool ist ein spezialisierter Pool für Aufgaben, die in Teilaufgaben aufgeteilt werden können (Teile und herrsche). Er nutzt effizient Work-Stealing.
Beispiel: Rekursive Berechnung der Summe der Elemente eines Arrays.
\n\nimport java.util.concurrent.RecursiveTask;\nimport java.util.concurrent.ForkJoinPool;\n\npublic class SumTask extends RecursiveTask<Long> {\n private static final int THRESHOLD = 1000;\n private final int[] array;\n private final int start;\n private final int end;\n\n public SumTask(int[] array, int start, int end) {\n this.array = array;\n this.start = start;\n this.end = end;\n }\n\n @Override\n protected Long compute() {\n if (end - start <= THRESHOLD) {\n long sum = 0;\n for (int i = start; i < end; i++) {\n sum += array[i];\n }\n return sum;\n }\n int mid = (start + end) / 2;\n SumTask leftTask = new SumTask(array, start, mid);\n SumTask rightTask = new SumTask(array, mid, end);\n\n leftTask.fork(); // asynchroner Start\n long rightResult = rightTask.compute(); // berechnen