Wenn Ihr Code anfängt zu stottern: Einführung in Warteschlangen und Hintergrundaufgaben in Java
\n\nStellen Sie sich folgende Situation vor: Sie starten einen Webserver in Java. Ein Benutzer sendet eine Anfrage – es muss eine E-Mail versendet, ein PDF-Bericht generiert und die Statistik aktualisiert werden. Wenn Sie alles synchron ausführen, wartet der Benutzer 30 Sekunden. Sein Kaffee wird kalt und er wechselt zur Konkurrenz. Kommt Ihnen das bekannt vor?
\n\nIn der Welt der Hintergrundaufgaben in Java wird dies elegant gelöst: Wir werfen die Aufgabe in eine Warteschlange und geben dem Benutzer ein „200 OK“ zurück. Und dann, im Hintergrund, verarbeitet unser Worker in Ruhe die E-Mails, PDFs und Statistiken. Kein Chaos, nur Ordnung.
\n\nHeute werden wir uns ansehen, wie man Aufgabenwarteschlangen in Java implementiert – von der einfachsten Single-Thread-Variante bis hin zu industriellen Lösungen mit RabbitMQ und Kafka. Sie erfahren, wie Sie aufhören, Brände zu löschen, und beginnen, zuverlässige Systeme zu entwerfen.
\n\nWarum braucht man Warteschlangen? Drei Gründe, die Sie überraschen werden
\n\n- \n
- Asynchronität – der Benutzer wartet nicht. Die Antwortzeit des Servers sinkt von 30 Sekunden auf 5 Millisekunden. \n
- Lastverteilung – Spitzenlasten legen den Server nicht lahm. Aufgaben werden in der Warteschlange zwischengelagert und verarbeitet, sobald Ressourcen frei werden. \n
- Fehlertoleranz – fällt ein Worker aus, bleibt die Aufgabe in der Warteschlange. Ein anderer Worker übernimmt sie. Daten gehen nicht verloren. \n
In Java gibt es dafür integrierte Werkzeuge (ExecutorService, BlockingQueue) und externe Broker (RabbitMQ, Kafka). Die Wahl hängt vom Umfang ab. Beginnen wir im Kleinen.
Einfache Warteschlange in Java: ExecutorService und BlockingQueue
\n\nWenn Sie kein verteiltes System benötigen, sondern einfach nur schwere Arbeit in den Hintergrund verschieben möchten, verwenden Sie ThreadPoolExecutor. Dies ist ein integrierter Thread-Pool, der Aufgaben aus einer Warteschlange bezieht.
Beispiel: Thread-Pool zur Auftragsabwicklung
\n\nimport java.util.concurrent.*;\n\npublic class OrderProcessor {\n private final ExecutorService executor;\n\n public OrderProcessor(int threadCount) {\n // Erstellen eines Pools mit fester Thread-Anzahl\n this.executor = Executors.newFixedThreadPool(threadCount);\n }\n\n public void processOrder(Order order) {\n executor.submit(() -> {\n // Schwere Arbeit: Quittungserstellung, E-Mail-Versand, Lageraktualisierung\n System.out.println("Verarbeite Auftrag: " + order.getId());\n try {\n Thread.sleep(2000); // Simulation der Arbeit\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n System.out.println("Auftrag " + order.getId() + " verarbeitet");\n });\n }\n\n public void shutdown() {\n executor.shutdown();\n }\n\n public static void main(String[] args) {\n OrderProcessor processor = new OrderProcessor(4);\n for (int i = 0; i < 10; i++) {\n processor.processOrder(new Order(i));\n }\n processor.shutdown();\n }\n}\n\nclass Order {\n private final int id;\n public Order(int id) { this.id = id; }\n public int getId() { return id; }\n}\n\nWas passiert hier? Wir haben einen Pool mit 4 Threads erstellt. 10 Aufträge werden parallel in 4er-Gruppen verarbeitet. Die Aufgabenwarteschlange in Java wird intern von ThreadPoolExecutor verwaltet – standardmäßig eine LinkedBlockingQueue. Wenn alle Threads belegt sind, warten die Aufgaben in der Warteschlange.
Erweiterte Konfiguration: Prioritäten und Fehlertoleranz
\n\nWas aber, wenn Aufgaben dringend sind? Verwenden Sie PriorityBlockingQueue:
BlockingQueue<Runnable> priorityQueue = new PriorityBlockingQueue<>(\n 10,\n Comparator.comparingInt(task -> ((PriorityTask) task).getPriority())\n);\n\nThreadPoolExecutor executor = new ThreadPoolExecutor(\n 2, 4, 60, TimeUnit.SECONDS, priorityQueue\n);\n\nJetzt werden Aufgaben mit hoher Priorität