{"id":34113,"date":"2023-06-14T10:40:00","date_gmt":"2023-06-14T08:40:00","guid":{"rendered":"https:\/\/www.happycoders.eu\/?p=34113"},"modified":"2025-09-09T14:09:26","modified_gmt":"2025-09-09T12:09:26","slug":"structured-concurrency-structuredtaskscope","status":"publish","type":"post","link":"https:\/\/www.happycoders.eu\/de\/java\/structured-concurrency-structuredtaskscope\/","title":{"rendered":"Structured Concurrency in Java mit StructuredTaskScope"},"content":{"rendered":"\n<p><em>Structured Concurrency<\/em> wurde \u2013 zusammen mit <a href=\"\/de\/java\/virtual-threads\/\">virtuellen Threads<\/a> und <a href=\"\/de\/java\/scoped-values\/\">Scoped Values<\/a> \u2013 in <a href=\"https:\/\/openjdk.org\/projects\/loom\/\" target=\"_blank\" rel=\"noopener\">Project Loom<\/a> entwickelt. <em>Structured Concurrency<\/em> ist seit <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-19-features\/\">Java 19<\/a> als Incubator-Feature und seit <a href=\"\/de\/java\/java-21-features\/\">Java 21<\/a> als Preview-Feature im JDK enthalten.<\/p>\n\n\n\n<div class=\"wp-block-uagb-info-box uagb-block-af9d0506 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top hc-infobox\"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">In <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-25-features\/\">Java 25<\/a> wird die <code>StructuredTaskScope<\/code>-API durch <a href=\"https:\/\/openjdk.org\/jeps\/505\" target=\"_blank\" rel=\"noopener\">JDK Enhancement Proposal 505<\/a> grundlegend \u00fcberarbeitet. Da Java 25 noch nicht released ist und du <em>Structured Concurrency<\/em> evtl. mit einer \u00e4lteren Java-Version ausprobieren m\u00f6chtest, habe ich alle Code-Beispiele in zwei Varianten angegeben: f\u00fcr Java 21\u201324 und f\u00fcr Java 25.<\/p><\/div><\/div>\n\n\n\n<p>In diesem Artikel erf\u00e4hrst du:<\/p>\n\n\n\n<ul class=\"wp-block-list hc-checked-list\">\n<li>Warum ben\u00f6tigen wir Structured Concurrency?<\/li>\n\n\n\n<li>Was ist Structured Concurrency?<\/li>\n\n\n\n<li>Wie wird <code>StructuredTaskScope<\/code> verwendet?<\/li>\n\n\n\n<li>Was ist eine <em>Policy<\/em>? Welche Policies gibt es, und wie k\u00f6nnen wir selbst eine Policy schreiben?<\/li>\n\n\n\n<li>Was ist der Vorteil von Structured Concurrency?<\/li>\n<\/ul>\n\n\n\n<p>Eine begleitende Demo-Anwendung (mit Java-21- als auch Java-25-Code) findest du in diesem <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\" target=\"_blank\" rel=\"noopener\">GitHub-Repository<\/a>.<\/p>\n\n\n\n<p>Schauen wir uns zuerst einmal an, wie wir nebenl\u00e4ufige Teilaufgaben bisher implementiert haben.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"warum-benoetigen-wir-structured-concurrency\">Warum ben\u00f6tigen wir Structured Concurrency?<\/h2>\n\n\n\n<p>Wenn eine Aufgabe aus verschiedenen \u2013 vor allem blockierenden \u2013 Teilaufgaben besteht, die nebenl\u00e4ufig erledigt werden k\u00f6nnen (z.&nbsp;B. Zugriff auf Daten aus einer Datenbank oder Aufruf einer Remote API), so konnten wir hierf\u00fcr bisher das Java-Executor-Framework einsetzen.<\/p>\n\n\n\n<p>Das k\u00f6nnte dann z. B. so aussehen (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo1_invoice\/InvoiceGenerator3_ThreadPool.java\" target=\"_blank\" rel=\"noopener\">InvoiceGenerator3_ThreadPool<\/a> in der Demo-Anwendung):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">Invoice <span class=\"hljs-title\">createInvoice<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> orderId, <span class=\"hljs-keyword\">int<\/span> customerId, String language)<\/span>\n    <span class=\"hljs-keyword\">throws<\/span> InterruptedException, ExecutionException <\/span>{\n  Future&lt;Order&gt; orderFuture = \n      executor.submit(() -&gt; orderService.getOrder(orderId));\n\n  Future&lt;Customer&gt; customerFuture =\n      executor.submit(() -&gt; customerService.getCustomer(customerId));\n\n  Future&lt;InvoiceTemplate&gt; invoiceTemplateFuture =\n      executor.submit(() -&gt; invoiceTemplateService.getTemplate(language));\n\n  Order order = orderFuture.get();\n  Customer customer = customerFuture.get();\n  InvoiceTemplate invoiceTemplate = invoiceTemplateFuture.get();\n\n  <span class=\"hljs-keyword\">return<\/span> Invoice.generate(order, customer, invoiceTemplate);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Wir \u00fcbergeben die drei Teilaufgaben an den <code>Executor<\/code> und warten auf die Teilergebnisse. Der Happy Path ist schnell implementiert. Aber wie behandeln wir Ausnahmen?<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wenn in einem Subtask ein Fehler auftritt \u2013 wie k\u00f6nnen wir dann die anderen Subtasks abbrechen? Wenn im Beispiel oben <code>loadOrderFromOrderService(...)<\/code> fehlschl\u00e4gt, dann wirft <code>orderFuture.get()<\/code> eine Exception, die <code>createInvoice(...)<\/code>-Methode endet, und wir haben evtl. zwei noch weiterlaufende Threads.<\/li>\n\n\n\n<li>Wie k\u00f6nnen wir die Subtasks abbrechen, wenn der Parent Task (\u201eErstelle eine Rechung\u201d) abgebrochen wird \u2013 oder wenn die komplette Anwendung heruntergefahren wird?<\/li>\n\n\n\n<li>Wie k\u00f6nnen wir \u2013 in einem alternativen Use Case \u2013 verbleibende Subtasks abbrechen, wenn lediglich das Ergebnis eines einzigen Subtasks ben\u00f6tigt wird?<\/li>\n<\/ul>\n\n\n\n<p>Alles ist machbar, erfordert aber \u00e4u\u00dferst komplexen, schwer wartbaren Code (im GitHub-Repository findest du zwei Beispiele daf\u00fcr: <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo1_invoice\/InvoiceGenerator2b_CompletableFutureCancelling.java\" target=\"_blank\" rel=\"noopener\">InvoiceGenerator2b_CompletableFutureCancelling<\/a> und <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo1_invoice\/InvoiceGenerator4b_NewVirtualThreadPerTaskCancelling.java\" target=\"_blank\" rel=\"noopener\">InvoiceGenerator4b_NewVirtualThreadPerTaskCancelling<\/a>).<\/p>\n\n\n\n<p>Und was, wenn wir Code dieser Art debuggen m\u00f6chten? Ein Thread-Dump z. B. w\u00fcrde uns haufenweise Threads mit dem Namen \u201epool-X-thread-Y\u201d liefern \u2013 wir w\u00fcssten aber nicht, welcher Pool-Thread zu welchem aufrufenden Thread geh\u00f6rt, da sich alle aufrufenden Threads den Thread-Pool des Executors teilen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"was-ist-unstructured-concurrency\">Was ist Unstructured Concurrency?<\/h2>\n\n\n\n<p>\u201eUnstructured Concurrency\u201d bedeutet, dass unsere Tasks in einem Netz von Threads ablaufen, deren Start und Ende im Code schwer erkennbar ist. Eine saubere Fehlerbehandlung ist meist nicht vorhanden, und oft kommt es zu verwaisten Threads, wenn eine Kontrollstruktur (im Beispiel oben: die <code>createInvoice(...)<\/code>-Methode) endet:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-half_400\"><img decoding=\"async\" width=\"400\" height=\"341\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-400x341.png\" alt=\"Unstructured Concurrency\" class=\"wp-image-37257\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-400x341.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-224x191.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-336x286.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-504x430.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-672x573.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2-600x512.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/unstructured-concurrency.v2.png 800w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><figcaption class=\"wp-element-caption\">Unstructured Concurrency<\/figcaption><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"was-ist-structured-concurrency\">Was ist Structured Concurrency?<\/h2>\n\n\n\n<p>Die in <a href=\"\/de\/java\/java-19-features\/#Structured_Concurrency_Incubator_-_JEP_428\">Java 19<\/a> als Incubator und <a href=\"\/de\/java\/java-21-features\/#Structured_Concurrency_Preview_-_JEP_453\">Java 21<\/a> als Preview eingef\u00fchrte \u2013 und in Java 25 noch einmal \u00fcberarbeitete \u2013 <em>Structured Concurrency<\/em> ist ein Konzept, das die Implementierung, Lesbarkeit und Wartbarkeit von Code f\u00fcr die Aufteilung einer Aufgabe in Teilaufgaben und deren nebenl\u00e4ufige Abarbeitung erheblich verbessert.<\/p>\n\n\n\n<p>Dazu f\u00fchrt sie mit der Klasse <code>StructuredTaskScope<\/code> eine Kontrollstruktur ein, die <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>einen klaren Scope definiert, an dessen Anfang die Threads der Teilaufgaben starten und an dessen Ende die Threads der Teilaufgaben enden,<\/li>\n\n\n\n<li>die eine saubere Fehlerbehandlung erm\u00f6glicht<\/li>\n\n\n\n<li>und die einen sauberen Abbruch von Teilaufgaben erlaubt, deren Ergebnisse nicht mehr ben\u00f6tigten werden.<\/li>\n<\/ul>\n\n\n\n<p>Was das genau bedeutet, zeige ich dir in den folgenden Abschnitten an mehreren Beispielen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-beispiel\">StructuredTaskScope Beispiel<\/h2>\n\n\n\n<p><em>Structured Concurrency<\/em> wird mit der Klasse <code>StructuredTaskScope<\/code> implementiert. Mit dieser Klasse k\u00f6nnen wir das Beispiel wie folgt umschreiben.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-beispiel-java-21-24\">StructuredTaskScope Beispiel \u2013 Java 21\u201324<\/h3>\n\n\n\n<p>Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo1_invoice\/InvoiceGenerator5_StructuredTaskScope.java\" target=\"_blank\" rel=\"noopener\">InvoiceGenerator5_StructuredTaskScope.java<\/a> im <code>java-21<\/code>-Branch der Demo-Anwendung:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">Invoice <span class=\"hljs-title\">createInvoice<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> orderId, <span class=\"hljs-keyword\">int<\/span> customerId, String language)<\/span>\n    <span class=\"hljs-keyword\">throws<\/span> InterruptedException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope&lt;&gt;()) {\n    Subtask&lt;Order&gt; orderSubtask = \n        scope.fork(() -&gt; orderService.getOrder(orderId));\n\n    Subtask&lt;Customer&gt; customerSubtask = \n        scope.fork(() -&gt; customerService.getCustomer(customerId));\n\n    Subtask&lt;InvoiceTemplate&gt; invoiceTemplateSubtask =\n        scope.fork(() -&gt; invoiceTemplateService.getTemplate(language));\n\n    scope.join();\n\n    Order order = orderSubtask.get();\n    Customer customer = customerSubtask.get();\n    InvoiceTemplate template = invoiceTemplateSubtask.get();\n\n    <span class=\"hljs-keyword\">return<\/span> Invoice.generate(order, customer, template);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Eine gemeinsame Erkl\u00e4rung f\u00fcr alle Java-Versionen folgt unter dem Java-25-Beispiel.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-beispiel-java-25\">StructuredTaskScope Beispiel \u2013 Java 25<\/h3>\n\n\n\n<p>Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo1_invoice\/InvoiceGenerator5_StructuredTaskScope.java\" target=\"_blank\" rel=\"noopener\">InvoiceGenerator5_StructuredTaskScope.java<\/a> im <code>main<\/code>-Branch der Demo-Anwendung:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">Invoice <span class=\"hljs-title\">createInvoice<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> orderId, <span class=\"hljs-keyword\">int<\/span> customerId, String language)<\/span>\n    <span class=\"hljs-keyword\">throws<\/span> InterruptedException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = StructuredTaskScope.open()) {\n    Subtask&lt;Order&gt; orderSubtask = \n        scope.fork(() -&gt; orderService.getOrder(orderId));\n\n    Subtask&lt;Customer&gt; customerSubtask = \n        scope.fork(() -&gt; customerService.getCustomer(customerId));\n\n    Subtask&lt;InvoiceTemplate&gt; invoiceTemplateSubtask =\n        scope.fork(() -&gt; invoiceTemplateService.getTemplate(language));\n\n    scope.join();\n\n    Order order = orderSubtask.get();\n    Customer customer = customerSubtask.get();\n    InvoiceTemplate template = invoiceTemplateSubtask.get();\n\n    <span class=\"hljs-keyword\">return<\/span> Invoice.generate(order, customer, template);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In diesem einfachen Beispiel unterscheidet sich lediglich die Art, wie der <code>StructuredTaskScope<\/code> ge\u00f6ffnet wird: vor Java 25 mit <code>new StructuredTaskScope&lt;&gt;()<\/code> und ab Java 25 mit <code>StructuredTaskScope.open()<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"erlaeuterungen-fuer-alle-java-versionen\">Erl\u00e4uterungen f\u00fcr alle Java-Versionen<\/h3>\n\n\n\n<p>Verglichen mit dem <em>Un<\/em>structured-Concurrency-Beispiel ersetzen wir den im Scope der <em>Klasse<\/em> liegenden <code>ExecutorService<\/code> durch einen im Scope der <em>Methode<\/em> liegenden <code>StructuredTaskScope<\/code> \u2013 und <code>executor.submit()<\/code> durch <code>scope.fork()<\/code>.<\/p>\n\n\n\n<p>Mit <code>scope.join()<\/code> warten wir darauf, dass alle Tasks erledigt sind. Das Risiko verwaister Tasks besteht damit nicht mehr.<\/p>\n\n\n\n<p>Danach k\u00f6nnen wir \u00fcber <code>Subtask.get()<\/code> die Ergebnisse der drei Tasks auslesen. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-fehlerbehandlung-java-21-24\">StructuredTaskScope Fehlerbehandlung \u2013 Java 21\u201324<\/h3>\n\n\n\n<p>Sollte es in einem der Tasks zu einer Exception gekommen sein, wirft <code>Subtask.get()<\/code> eine <code>IllegalStateException<\/code>. Besser ist es daher mit <code>state()<\/code> den Status eines Subtasks abzufragen, bevor wir <code>get()<\/code> aufrufen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Order order;\n<span class=\"hljs-keyword\">if<\/span> (orderSubtask.state() == Subtask.State.SUCCESS) {\n  order = orderSubtask.get();\n} <span class=\"hljs-keyword\">else<\/span> {\n  <span class=\"hljs-comment\">\/\/ Handle error<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-fehlerbehandlung-java-25\">StructuredTaskScope Fehlerbehandlung \u2013 Java 25<\/h3>\n\n\n\n<p>Wenn es in Java 25 zu einer Exception in einem der Tasks kommt, wird diese \u2013 gewrappt in eine <code>StructuredTaskScope.FailedException<\/code> \u2013 bereits von <code>scope.join()<\/code> geworfen. Eine Pr\u00fcfung des Subtask-Status nach dem Aufruf von <code>scope.join()<\/code> ist daher nicht erforderlich.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"den-beispielcode-ausfuehren\">Den Beispielcode ausf\u00fchren<\/h3>\n\n\n\n<p>Falls du das Beispiel selbst ausprobieren m\u00f6chtest: Preview-Features m\u00fcssen explizit freigeschaltet werden, in diesem Fall mit <code>--enable-preview --source &lt;verwendete Java-Version&gt;<\/code>. Eine genaue Anleitung findest du in der <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/README.md\" target=\"_blank\" rel=\"noopener\">README<\/a> der Demo-Anwendung.<\/p>\n\n\n<div class=\"convertkit-form wp-block-convertkit-form\" style=\"\"><script async data-uid=\"1427197203\" src=\"https:\/\/happycoders.kit.com\/1427197203\/index.js\" data-jetpack-boost=\"ignore\" data-no-defer=\"1\" data-no-optimize=\"1\" nowprocket><\/script><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-policies\">StructuredTaskScope Policies<\/h2>\n\n\n\n<p>Eine sogenannte <em>Policy<\/em> definiert, was passiert, wenn ein Subtask beendet wird oder eine Exception wirft. Au\u00dferdem kann eine Policy einen R\u00fcckgabewert f\u00fcr <code>scope.join()<\/code> definieren \u2013 dazu sp\u00e4ter mehr.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"policies-in-java-21-24\">Policies in Java 21\u201324<\/h3>\n\n\n\n<p>Vor Java 25 hatte ein durch <code>new StructuredTaskScope()<\/code> ge\u00f6ffneter Scope die Policy, darauf zu warten, dass alle Subtasks erfolgreich oder mit einer Exception beendet wurden.<\/p>\n\n\n\n<p>Wenn aber in einem der Tasks in unserem Beispiel eine Exception auftritt, k\u00f6nnen wir mit den Ergebnissen der anderen zwei Tasks nichts anfangen \u2013 warum also auf sie warten?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"java-21-24-shutdown-on-failure-policy\">Java 21\u201324: \u201eShutdown on Failure\u201d-Policy<\/h3>\n\n\n\n<p>Mit der \u201eShutdown on Failure\u201d-Policy k\u00f6nnen wir in Java 21\u201324 festlegen, dass das Auftreten einer Exception in einem Task dazu f\u00fchrt, dass alle anderen Tasks abgebrochen werden.<\/p>\n\n\n\n<p>Die \u201eShutdown on Failure\u201d-Policy k\u00f6nnen wir wie folgt einsetzen (du findest den Code in der Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo1_invoice\/InvoiceGenerator6_ShutdownOnFailure.java\" target=\"_blank\" rel=\"noopener\">InvoiceGenerator6_ShutdownOnFailure<\/a> im <code>java-21<\/code>-Branch des GitHub-Repos):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">Invoice <span class=\"hljs-title\">createInvoice<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> orderId, <span class=\"hljs-keyword\">int<\/span> customerId, String language)<\/span>\n    <span class=\"hljs-keyword\">throws<\/span> InterruptedException, ExecutionException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope.ShutdownOnFailure()) {\n    Subtask&lt;Order&gt; orderSubtask = \n        scope.fork(() -&gt; orderService.getOrder(orderId));\n\n    Subtask&lt;Customer&gt; customerSubtask = \n        scope.fork(() -&gt; customerService.getCustomer(customerId));\n\n    Subtask&lt;InvoiceTemplate&gt; invoiceTemplateSubtask =\n        scope.fork(() -&gt; invoiceTemplateService.getTemplate(language));\n\n    scope.join();\n    scope.throwIfFailed();\n\n    Order order = orderSubtask.get();\n    Customer customer = customerSubtask.get();\n    InvoiceTemplate template = invoiceTemplateSubtask.get();\n\n    <span class=\"hljs-keyword\">return<\/span> Invoice.generate(order, customer, template);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Verglichen mit dem vorherigen Beispiel habe ich zwei Dinge ge\u00e4ndert:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ich habe in der dritten Zeile <code>new StructuredTaskScope&lt;&gt;()<\/code> durch <code>new StructuredTaskScope.ShutdownOnFailure()<\/code> ersetzt.<\/li>\n\n\n\n<li>Ich habe nach <code>scope.join()<\/code> das Kommando <code>scope.throwIfFailed()<\/code> eingef\u00fcgt.<\/li>\n<\/ul>\n\n\n\n<p>Sollte nun in einem der drei Tasks eine Exception auftreten, werden alle anderen Subtasks sofort abgebrochen, <code>scope.join()<\/code> kehrt zur\u00fcck und <code>scope.throwIfFailed()<\/code> wirft die Exception des fehlgeschlagenen Subtasks, eingebettet in eine <code>ExecutionException<\/code>.<\/p>\n\n\n\n<p>Im Beispielcode werfen die drei Subtasks mit einer gewissen Wahrscheinlichkeit eine Exception. Wenn du das Programm ein paar mal startest, wirst du sehen, wie eine Exception in einem Task zu einer Interruption in den anderen Tasks und einer Beendigung des Programms f\u00fchrt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">$ java -cp target\/classes --enable-preview eu.happycoders.structuredconcurrency\/demo1_invoice\/InvoiceGenerator6_ShutdownOnFailure\n&#091;Thread&#091;#1,main,5,main]] Forking tasks\n&#091;Thread&#091;#1,main,5,main]] Waiting for all tasks to finish or one to fail\n&#091;VirtualThread&#091;#31]\/runnable@ForkJoinPool-1-worker-2] Loading customer\n&#091;VirtualThread&#091;#29]\/runnable@ForkJoinPool-1-worker-3] Loading order\n&#091;VirtualThread&#091;#35]\/runnable@ForkJoinPool-1-worker-1] Loading template\n&#091;VirtualThread&#091;#31]\/runnable@ForkJoinPool-1-worker-1] Finished loading customer\n&#091;VirtualThread&#091;#29]\/runnable@ForkJoinPool-1-worker-2] Error loading order\n&#091;VirtualThread&#091;#35]\/runnable@ForkJoinPool-1-worker-1] Template loading was interrupted\nException in thread \"main\" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Error loading order\n        &#091;...]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>An dieser Ausgabe siehst du \u00fcbrigens auch, dass alle Tasks in <a href=\"\/de\/java\/virtual-threads\/\">virtuellen Threads<\/a> ausgef\u00fchrt werden.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"policies-in-java-25\">Policies in Java 25<\/h3>\n\n\n\n<p>Ein in Java 25 mit <code>StructuredTaskScope.open()<\/code> ge\u00f6ffneter Scope hat die Policy, beim Auftreten einer Exception in einem der Subtasks umgehend alle anderen Subtasks abzubrechen und \u00fcber <code>scope.join()<\/code> eine <code>FailedException<\/code> zu werfen mit der im Subtask aufgetreteten Exception als \u201eCause\u201c.<\/p>\n\n\n\n<p>Also \u201eShutdown on Failure\u201d by default.<\/p>\n\n\n\n<p>Ein Aufruf von <code>scope.throwIfFailed()<\/code> wie in Java 21\u201324 ist nicht erforderlich \u2013 die <code>throwIfFailed()<\/code>-Methode existiert in Java 25 nicht mehr.<\/p>\n\n\n\n<p>In Java 25 sieht die Programmausgabe beim Auftreten eines Fehlers beispielsweise wie folgt aus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">&#091;Thread&#091;#3,main,5,main]] Forking tasks\n&#091;VirtualThread&#091;#37]\/runnable@ForkJoinPool-1-worker-1] Loading order\n&#091;VirtualThread&#091;#39]\/runnable@ForkJoinPool-1-worker-4] Loading customer\n&#091;Thread&#091;#3,main,5,main]] Waiting for all tasks to finish\n&#091;VirtualThread&#091;#44]\/runnable@ForkJoinPool-1-worker-3] Loading template\n&#091;VirtualThread&#091;#39]\/runnable@ForkJoinPool-1-worker-3] Error loading customer\n&#091;VirtualThread&#091;#37]\/runnable@ForkJoinPool-1-worker-1] Order loading was interrupted\n&#091;VirtualThread&#091;#44]\/runnable@ForkJoinPool-1-worker-1] Template loading was interrupted\nException in thread \"main\" java.util.concurrent.StructuredTaskScope$FailedException: java.lang.RuntimeException: Error loading customer<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Du siehst: Statt einer generischen <code>ExecutionException<\/code> wie in Java 21\u201324 erhalten wir ab Java 25 eine <code>StructuredTaskScope<\/code>-spezifische <code>FailedException<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"java-21-24-shutdown-on-success-policy\">Java 21\u201324: \u201eShutdown on Success\u201d-Policy<\/h3>\n\n\n\n<p>Eine alternative Policy ist \u201eShutdown on Success\u201d. Bei dieser Policy wird der Scope beendet, sobald <em>ein<\/em> Subtask erfolgreich war. Die anderen Subtasks werden dann abgebrochen.<\/p>\n\n\n\n<p>In Java 21\u201324 erzeugst du einen Scope mit der \u201eShutdown on Success\u201d-Policy durch <code>new StructuredTaskScope.ShutdownOnSuccess()<\/code>. Das Ergebnis des einen erfolgreichen Subtasks kannst du mit <code>scope.result()<\/code> auslesen.<\/p>\n\n\n\n<p>Hier ein Beispiel dazu \u2013 mit einem anderen Use Case: Wir wollen eine Kundenadresse \u00fcber mehrere externe APIs gleichzeitig verifizieren und wollen nur das erste Ergebnis verwenden (du findest den Code in der Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo2_address\/AddressVerification2_ShutdownOnSuccess.java\" target=\"_blank\" rel=\"noopener\">AddressVerification2_ShutdownOnSuccess<\/a> im <code>java-21<\/code>-Branch des GitHub-Repos):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">AddressVerificationResponse <span class=\"hljs-title\">verifyAddress<\/span><span class=\"hljs-params\">(Address address)<\/span>\n    <span class=\"hljs-keyword\">throws<\/span> InterruptedException, ExecutionException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope.ShutdownOnSuccess&lt;AddressVerificationResponse&gt;()) {\n    scope.fork(() -&gt; verificationService.verifyViaServiceA(address));\n    scope.fork(() -&gt; verificationService.verifyViaServiceB(address));\n    scope.fork(() -&gt; verificationService.verifyViaServiceC(address));\n\n    scope.join();\n\n    <span class=\"hljs-keyword\">return<\/span> scope.result();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hier wartet <code>scope.join()<\/code> darauf, dass der erste Subtask erfolgreich beendet wird \u2013 danach liefert <code>scope.result()<\/code> dessen Ergebnis zur\u00fcck. Sollten wider Erwarten alle drei Aufrufe der <code>verifyViaServiceX()<\/code>-Methoden eine Exception geworfen haben, wirft <code>scope.result()<\/code> die erste davon, eingebettet in eine <code>ExecutionException<\/code>.<\/p>\n\n\n\n<p>Wenn du den Beispielcode ausf\u00fchrst, siehst du, wie der erste erfolgreiche Subtask zu einem Ergebnis f\u00fchrt und die anderen Tasks abgebrochen werden:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">$ java -cp target\/classes --enable-preview eu.happycoders.structuredconcurrency\/demo2_address\/AddressVerification2_ShutdownOnSuccess\n&#091;Thread&#091;#1,main,5,main]] Forking tasks\n&#091;Thread&#091;#1,main,5,main]] Waiting for one task to finish\n&#091;VirtualThread&#091;#31]\/runnable@ForkJoinPool-1-worker-2] Verifying address via service B\n&#091;VirtualThread&#091;#29]\/runnable@ForkJoinPool-1-worker-3] Verifying address via service A\n&#091;VirtualThread&#091;#34]\/runnable@ForkJoinPool-1-worker-1] Verifying address via service C\n&#091;VirtualThread&#091;#34]\/runnable@ForkJoinPool-1-worker-1] Finished loading address via service C\n&#091;Thread&#091;#1,main,5,main]] Retrieving result\n&#091;VirtualThread&#091;#31]\/runnable@ForkJoinPool-1-worker-3] Verifying address via service B was interrupted\n&#091;VirtualThread&#091;#29]\/runnable@ForkJoinPool-1-worker-2] Verifying address via service A was interrupted<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Beachte, dass die <code>result()<\/code>-Methode nur bei <code>ShutdownOnSuccess<\/code> verf\u00fcgbar ist und die <code>throwIfFailed()<\/code>-Methode nur bei <code>ShutdownOnFailure<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"java-25-any-successful-result-policy\">Java 25: \u201eAny Successful Result\u201d-Policy<\/h3>\n\n\n\n<p>In Java 25 hei\u00dft die entsprechende Policy \u201eAny Successful Result\u201d. Auch bei dieser wird der Scope beendet, sobald der erste Subtask erfolgreich war. Die anderen Subtasks werden abgebrochen.<\/p>\n\n\n\n<p>In Java 25 wird eine Policy nicht mehr durch Ableiten der <code>StructuredTaskScope<\/code>-Klasse implementiert, wie es bei Java 21\u201324 der Fall war. Policies werden ab Java 25 durch sogenannte <em>Joiner<\/em> implementiert.<\/p>\n\n\n\n<p>Ein Joiner f\u00fcr die \u201eAny Successful Result\u201d-Policy wird \u00fcber die statische Methode <code>Joiner.anySuccessfulResultOrThrow()<\/code> erzeugt.<\/p>\n\n\n\n<p>Der folgende Code zeigt, wie das Adress-\u00dcberpr\u00fcfungs-Beispiel mit Java 25 implementiert wird (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo2_address\/AddressVerification2_AnySuccessfulResult.java\" target=\"_blank\" rel=\"noopener\">AddressVerification2_AnySuccessfulResult<\/a> im <code>main<\/code>-Branch des GitHub-Repos):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">AddressVerificationResponse <span class=\"hljs-title\">verifyAddress<\/span><span class=\"hljs-params\">(Address address)<\/span> <span class=\"hljs-keyword\">throws<\/span> InterruptedException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope =\n      StructuredTaskScope.open(\n          Joiner.&lt;AddressVerificationResponse&gt;anySuccessfulResultOrThrow())) {\n    log(<span class=\"hljs-string\">\"Forking tasks\"<\/span>);\n\n    scope.fork(() -&gt; verificationService.verifyViaServiceA(address));\n    scope.fork(() -&gt; verificationService.verifyViaServiceB(address));\n    scope.fork(() -&gt; verificationService.verifyViaServiceC(address));\n\n    log(<span class=\"hljs-string\">\"Waiting for one task to finish\"<\/span>);\n\n    <span class=\"hljs-keyword\">return<\/span> scope.join();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>\u00c4nderungen gegen\u00fcber Java 21\u201324:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Der Scope wird mit <code>StructuredTaskScope.open(Joiner.anySuccessfulResultOrThrow()))<\/code> ge\u00f6ffnet \u2013 nicht mehr mit <code>new StructuredTaskScope.ShutdownOnSuccess()<\/code>.<\/li>\n\n\n\n<li>Die <code>scope.result()<\/code>-Methode f\u00e4llt weg \u2013 stattdessen liefert <code>scope.join()<\/code> das Ergebnis zur\u00fcck.<\/li>\n\n\n\n<li>Sollten alle drei Subtasks fehlschlagen, wirft <code>scope.join()<\/code> eine <code>FailedException<\/code> statt einer <code>ExecutionException<\/code>.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"java-25-alle-policies\">Java 25: Alle Policies<\/h3>\n\n\n\n<p>In Java 21\u201324 gibt es nur die oben gezeigten Policies \u201eShutdown on Failure\u201d und \u201eShutdown on Success\u201d.<\/p>\n\n\n\n<p>In Java 25 stehen deutlich mehr Policies zur Auswahl:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table><thead><tr><th>Joiner<\/th><th>Beschreibung<\/th><\/tr><\/thead><tbody><tr><td>ohne Joiner<br><br>oder<br><br><code>Joiner.awaitAllSuccessfulOrThrow()<\/code><\/td><td>Eine Exception in einem Subtask f\u00fchrt umgehend zu einem Abbruch des Scopes; <code>scope.join()<\/code> wirft die Exception gewrappt in eine <code>FailedException<\/code>.<br><br>Wenn alle Subtasks erfolgreich abgeschlossen sind, endet <code>scope.join()<\/code> ohne Exception und gibt <code>null<\/code> zur\u00fcck. Die Ergebnisse der Subtasks m\u00fcssen aus den von <code>scope.fork()<\/code> zur\u00fcckgegebenen <code>Subtask<\/code>-Objekten ausgelesen werden.<br><br>Entspricht in Java 21\u201324 der \u201eShutdown on Failure\u201d-Policy.<\/td><\/tr><tr><td><code>Joiner.awaitAll()<\/code><\/td><td><code>scope.join()<\/code> wartet darauf, dass alle Subtasks beendet sind \u2013 egal ob erfolgreich oder nicht. <br><br><code>scope.join()<\/code> gibt in jedem Fall <code>null<\/code> zur\u00fcck; die Ergebnisse der Subtasks m\u00fcssen aus den von <code>scope.fork()<\/code> zur\u00fcckgegebenen <code>Subtask<\/code>-Objekten ausgelesen werden.<br><br>Entspricht in Java 21\u201324 dem Standardverhalten.<\/td><\/tr><tr><td><code>Joiner<\/code><code>.anySuccessfulResultOrThrow()<\/code><\/td><td><code>scope.join()<\/code> liefert das Ergebnis des ersten erfolgreichen Subtasks; andere Subtasks werden abgebrochen. Schlagen alle Subtasks fehl, wirft <code>scope.join()<\/code> die Exception des ersten fehlgeschlagenen Subtasks gewrappt in eine <code>FailedException<\/code>.<br><br>Entspricht in Java 21\u201324 der \u201eShutdown on Success\u201d-Policy.<\/td><\/tr><tr><td><code>Joiner.allSuccessfulOrThrow()<\/code><\/td><td>Entspricht der grunds\u00e4tzlichen Funktionsweise ohne Joiner bzw. mit <code>Joiner.awaitAllSuccessfulOrThrow()<\/code> \u2013 mit dem Unterschied, dass <code>scope.join()<\/code> bei Erfolg einen Stream aller Subtasks zur\u00fcckliefert.<br><br>Eine Exception in einem Subtask f\u00fchrt umgehend zu einem Abbruch des Scopes; <code>scope.join()<\/code> wirft die Exception gewrappt in eine <code>FailedException<\/code>.<br><br>Wenn alle Subtasks erfolgreich abgeschlossen sind, endet <code>scope.join()<\/code> ohne Exception und gibt einen Stream aller Subtasks zur\u00fcck.<\/td><\/tr><tr><td><code>Joiner.allUntil(Predicate isDone)<\/code><\/td><td><code>scope.join()<\/code> wartet darauf, dass entweder alle Subtasks beendet sind \u2013 egal ob erfolgreich oder nicht \u2013 oder dass das \u00fcbergebene <code>Predicate<\/code> auf wenigstens einen beendeten Subtask matcht. <br><br>Liefert wie <code>Joiner.allSuccessfulOrThrow()<\/code> einen Stream der Subtasks zur\u00fcck.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"benutzerdefinierte-structuredtaskscope-policy\">Benutzerdefinierte StructuredTaskScope-Policy<\/h2>\n\n\n\n<p>Sollte keine der vordefinierten Policies f\u00fcr deinen Einsatzzweck geeignet sein, kannst du mit relativ geringem Aufwand eine eigene Policy schreiben.<\/p>\n\n\n\n<p>Nehmen wir an, wir wollen die Verf\u00fcgbarkeit eines Produkts bei mehreren Lieferanten pr\u00fcfen, und wir wollen nicht das erste Ergebnis verwenden, sondern das mit der schnellsten Verf\u00fcgbarkeit. Gleichzeitig wollen wir fehlgeschlagene Anfragen nur dann propagieren, wenn die Anfragen bei <em>allen<\/em> Lieferanten fehlgeschlagen sind.<\/p>\n\n\n\n<p>Das l\u00e4sst sich \u00fcberraschend einfach \u2013 und zugleich f\u00fcr andere Einsatzszenarien wiederverwendbar \u2013 realisieren.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"benutzerdefinierte-policy-in-java-21-24\">Benutzerdefinierte Policy in Java 21\u201324<\/h3>\n\n\n\n<p>Hier zun\u00e4chst die Policy f\u00fcr Java 21\u201324 (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/BestResultScope.java\" target=\"_blank\" rel=\"noopener\">BestResultScope<\/a> im <code>java-21<\/code>-Branch des GitHub-Repos). Diese nimmt als Konstruktor-Parameter einen <code>Comparator<\/code> entgegen, \u00fcber den wir sp\u00e4ter definieren werden, dass wir als <em>best result<\/em> \u2013 also als bestes Ergebnis \u2013 die schnellste Verf\u00fcgbarkeit erwarten.<\/p>\n\n\n\n<p><code>BestResultScope<\/code> erweitert zudem die Klasse <code>StructuredTaskScope<\/code>, \u00fcberschreibt deren <code>handleComplete(\u2026)<\/code>-Methode und f\u00fcgt eine <code>resultOrElseThrow()<\/code>-Methode hinzu:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BestResultScope<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">StructuredTaskScope<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> Comparator&lt;T&gt; comparator;\n\n  <span class=\"hljs-keyword\">private<\/span> T bestResult;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> List&lt;Throwable&gt; exceptions = \n      Collections.synchronizedList(<span class=\"hljs-keyword\">new<\/span> ArrayList&lt;&gt;());\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">BestResultScope<\/span><span class=\"hljs-params\">(Comparator&lt;T&gt; comparator)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.comparator = comparator;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">handleComplete<\/span><span class=\"hljs-params\">(Subtask&lt;? extends T&gt; subtask)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">switch<\/span> (subtask.state()) {\n      <span class=\"hljs-keyword\">case<\/span> UNAVAILABLE -&gt; {\n        <span class=\"hljs-comment\">\/\/ Ignore<\/span>\n      }\n      <span class=\"hljs-keyword\">case<\/span> SUCCESS -&gt; {\n        T result = subtask.get();\n        <span class=\"hljs-keyword\">synchronized<\/span> (<span class=\"hljs-keyword\">this<\/span>) {\n          <span class=\"hljs-keyword\">if<\/span> (bestResult == <span class=\"hljs-keyword\">null<\/span> || comparator.compare(result, bestResult) &gt; <span class=\"hljs-number\">0<\/span>) {\n            bestResult = result;\n          }\n        }\n      }\n      <span class=\"hljs-keyword\">case<\/span> FAILED -&gt; exceptions.add(subtask.exception());\n    }\n  }\n\n  <span class=\"hljs-keyword\">public<\/span> &lt;X extends Throwable&gt; <span class=\"hljs-function\">T <span class=\"hljs-title\">resultOrElseThrow<\/span><span class=\"hljs-params\">(\n      Supplier&lt;? extends X&gt; exceptionSupplier)<\/span> <span class=\"hljs-keyword\">throws<\/span> X <\/span>{\n    ensureOwnerAndJoined();\n    <span class=\"hljs-keyword\">if<\/span> (bestResult != <span class=\"hljs-keyword\">null<\/span>) {\n      <span class=\"hljs-keyword\">return<\/span> bestResult;\n    } <span class=\"hljs-keyword\">else<\/span> {\n      X exception = exceptionSupplier.get();\n      exceptions.forEach(exception::addSuppressed);\n      <span class=\"hljs-keyword\">throw<\/span> exception;\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <code>handleComplete(\u2026)<\/code>-Methode wird f\u00fcr jeden beendeten Subtask aufgerufen \u2013 sowohl f\u00fcr erfolgreiche als auch f\u00fcr solche, die eine Exception geworfen haben. Welcher Fall eingetreten ist, pr\u00fcfen wir mit <code>subtask.state()<\/code>.<\/p>\n\n\n\n<p>Im Erfolgsfall holen wir mit <code>subtask.get()<\/code> das Resultat und schreiben dieses \u2013 sofern es besser ist als das bisher beste \u2013 threadsicher in das Feld <code>bestResult<\/code>.<\/p>\n\n\n\n<p>Im Fall einer Exception sammeln wir diese threadsicher in einer Liste.<\/p>\n\n\n\n<p>Die <code>resultOrElseThrow()<\/code>-Methode stellt zun\u00e4chst durch den Aufruf von <code>ensureOwnerAndJoined()<\/code> sicher, dass sie aus demjenigen Thread aufgerufen wurde, der den <code>StructuredTaskScope<\/code> erzeugt hat, und dass dieser Thread zuvor <code>join()<\/code> oder <code>joinUntil(\u2026)<\/code> aufgerufen hat. <\/p>\n\n\n\n<p>Daraufhin pr\u00fcft <code>resultOrElseThrow()<\/code>, ob ein erfolgreiches Ergebnis vorliegt, und gibt dieses zur\u00fcck. Andernfalls wirft es die spezifizierte Exception, an die es die gesammelten Exceptions als \u201esuppressed exceptions\u201d anh\u00e4ngt.<\/p>\n\n\n\n<p>Die benutzerdefinierte Policy k\u00f6nnen wir wie folgt einsetzen (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/SupplierDeliveryTimeCheck2_StructuredTaskScope.java\" target=\"_blank\" rel=\"noopener\">SupplierDeliveryTimeCheck2_StructuredTaskScope<\/a> im <code>java-21<\/code>-Branch des GitHub-Repos):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">SupplierDeliveryTime <span class=\"hljs-title\">getSupplierDeliveryTime<\/span><span class=\"hljs-params\">(String productId, List&lt;String&gt; supplierIds)<\/span>\n    <span class=\"hljs-keyword\">throws<\/span> SupplierDeliveryTimeCheckException, InterruptedException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope =\n      <span class=\"hljs-keyword\">new<\/span> BestResultScope&lt;&gt;(\n          Comparator.comparing(SupplierDeliveryTime::deliveryTimeHours).reversed())) {\n    <span class=\"hljs-keyword\">for<\/span> (String supplierId : supplierIds) {\n      scope.fork(() -&gt; service.getDeliveryTime(productId, supplierId));\n    }\n\n    scope.join();\n    <span class=\"hljs-keyword\">return<\/span> scope.result();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die Ausgabe des Beispielprogramms k\u00f6nnte z. B. so aussehen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">$ java -cp target\/classes --enable-preview eu.happycoders.structuredconcurrency\/demo3_suppliers\/SupplierDeliveryTimeCheck2_StructuredTaskScope\n&#091;VirtualThread&#091;#31]\/runnable@ForkJoinPool-1-worker-2] Retrieving delivery time from supplier B\n&#091;VirtualThread&#091;#33]\/runnable@ForkJoinPool-1-worker-4] Retrieving delivery time from supplier D\n&#091;VirtualThread&#091;#34]\/runnable@ForkJoinPool-1-worker-3] Retrieving delivery time from supplier E\n&#091;VirtualThread&#091;#32]\/runnable@ForkJoinPool-1-worker-5] Retrieving delivery time from supplier C\n&#091;VirtualThread&#091;#29]\/runnable@ForkJoinPool-1-worker-3] Retrieving delivery time from supplier A\n&#091;VirtualThread&#091;#31]\/runnable@ForkJoinPool-1-worker-3] Error retrieving delivery time from supplier B\n&#091;VirtualThread&#091;#29]\/runnable@ForkJoinPool-1-worker-5] Finished retrieving delivery time from supplier A: 110 hours\n&#091;VirtualThread&#091;#32]\/runnable@ForkJoinPool-1-worker-3] Finished retrieving delivery time from supplier C: 104 hours\n&#091;VirtualThread&#091;#34]\/runnable@ForkJoinPool-1-worker-3] Error retrieving delivery time from supplier E\n&#091;VirtualThread&#091;#33]\/runnable@ForkJoinPool-1-worker-3] Finished retrieving delivery time from supplier D: 51 hours\n&#091;Thread&#091;#1,main,5,main]] Response: SupplierDeliveryTime&#091;supplier=D, deliveryTimeHours=51]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Es ist sch\u00f6n zu sehen, wie zwar der Aufruf f\u00fcr die Lieferanten B und E fehlerhaft war, die restlichen Lieferanten aber Ergebnisse geliefert haben und schlie\u00dflich das beste Ergebnis \u2013 Lieferant D mit 51 Stunden Lieferzeit \u2013 zur\u00fcckgeliefert wird.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"benutzerdefinierte-policy-in-java-25\">Benutzerdefinierte Policy in Java 25<\/h3>\n\n\n\n<p>In Java 25 definierten wir eine benutzterdefinierte Policy nicht durch Erweitern von <code>StructuredTaskScope<\/code>, sondern durch Implementierung des <code>Joiner<\/code>-Interfaces.<\/p>\n\n\n\n<p>Hier der entsprechende Code f\u00fcr den Joiner (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/BestResultJoiner.java\" target=\"_blank\" rel=\"noopener\">BestResultJoiner<\/a> im <code>main<\/code>-Branch des GitHub-Repos):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BestResultJoiner<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">Joiner<\/span>&lt;<span class=\"hljs-title\">T<\/span>, <span class=\"hljs-title\">T<\/span>&gt; <\/span>{\n\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> Comparator&lt;T&gt; comparator;\n\n  <span class=\"hljs-keyword\">private<\/span> T bestResult;\n  <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> List&lt;Throwable&gt; exceptions = \n      Collections.synchronizedList(<span class=\"hljs-keyword\">new<\/span> ArrayList&lt;&gt;());\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">BestResultJoiner<\/span><span class=\"hljs-params\">(Comparator&lt;T&gt; comparator)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">this<\/span>.comparator = comparator;\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">boolean<\/span> <span class=\"hljs-title\">onComplete<\/span><span class=\"hljs-params\">(Subtask&lt;? extends T&gt; subtask)<\/span> <\/span>{\n    <span class=\"hljs-keyword\">switch<\/span> (subtask.state()) {\n      <span class=\"hljs-keyword\">case<\/span> UNAVAILABLE -&gt; {\n        <span class=\"hljs-comment\">\/\/ Ignore<\/span>\n      }\n      <span class=\"hljs-keyword\">case<\/span> SUCCESS -&gt; {\n        T result = subtask.get();\n        <span class=\"hljs-keyword\">synchronized<\/span> (<span class=\"hljs-keyword\">this<\/span>) {\n          <span class=\"hljs-keyword\">if<\/span> (bestResult == <span class=\"hljs-keyword\">null<\/span> || comparator.compare(result, bestResult) &gt; <span class=\"hljs-number\">0<\/span>) {\n            bestResult = result;\n          }\n        }\n      }\n      <span class=\"hljs-keyword\">case<\/span> FAILED -&gt; exceptions.add(subtask.exception());\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>; <span class=\"hljs-comment\">\/\/ Don't cancel the scope<\/span>\n  }\n\n  <span class=\"hljs-meta\">@Override<\/span>\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> T <span class=\"hljs-title\">result<\/span><span class=\"hljs-params\">()<\/span> <span class=\"hljs-keyword\">throws<\/span> SupplierDeliveryTimeCheckException <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (bestResult != <span class=\"hljs-keyword\">null<\/span>) {\n      <span class=\"hljs-keyword\">return<\/span> bestResult;\n    } <span class=\"hljs-keyword\">else<\/span> {\n      SupplierDeliveryTimeCheckException exception = \n          <span class=\"hljs-keyword\">new<\/span> SupplierDeliveryTimeCheckException();\n      exceptions.forEach(exception::addSuppressed);\n      <span class=\"hljs-keyword\">throw<\/span> exception;\n    }\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Die <code>onComplete()<\/code>-Methode entspricht gr\u00f6\u00dftenteils der vorherigen <code>handleComplete()<\/code>-Methode. Der einizge Unterschied: <code>onComplete()<\/code> gibt ein <code>boolean<\/code> zur\u00fcck, mit dem die Methode angeben kann, ob der Scope abgebrochen werden soll (<code>true<\/code> steht f\u00fcr abbrechen).<\/p>\n\n\n\n<p>Die <code>result()<\/code>-Methode entspricht gr\u00f6\u00dftenteils der vorherigen <code>resultOrElseThrow()<\/code>-Methode, allerdings bekommt die <code>result()<\/code>-Methode keinen Exception-<code>Supplier<\/code> \u00fcbergeben und muss selbst entscheiden, welche Exception sie wirft. M\u00f6chte man den Exception-Typ variabel gestalten wie im Beispiel f\u00fcr Java 21\u201324, k\u00f6nnte man den entsprechenden Supplier dem <code>Joiner<\/code> im Konstruktor \u00fcbergeben.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"verschachtelte-structuredtaskscopes\">Verschachtelte StructuredTaskScopes<\/h2>\n\n\n\n<p>Falls wir nicht nur die Lieferanten f\u00fcr <em>ein<\/em> Produkt gleichzeitig abfragen m\u00f6chten, sondern die Lieferanten f\u00fcr <em>mehrere<\/em> Produkte, so k\u00f6nnen wir das ganz einfach wie folgt l\u00f6sen:<\/p>\n\n\n\n<p>Hier zun\u00e4chst der Code f\u00fcr Java 21\u201324 (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/SupplierDeliveryTimeCheck3_NestedStructuredTaskScope.java\" target=\"_blank\" rel=\"noopener\">SupplierDeliveryTimeCheck3_NestedStructuredTaskScope<\/a> im <code>java-21<\/code>-Branch des GitHub-Repos):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">List&lt;SupplierDeliveryTime&gt; <span class=\"hljs-title\">getSupplierDeliveryTimes<\/span><span class=\"hljs-params\">(\n    List&lt;String&gt; productIds, List&lt;String&gt; supplierIds)<\/span> <span class=\"hljs-keyword\">throws<\/span> InterruptedException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope&lt;SupplierDeliveryTime&gt;()) {\n    List&lt;Subtask&lt;SupplierDeliveryTime&gt;&gt; subtasks =\n        productIds.stream()\n            .map(productId -&gt; \n                scope.fork(() -&gt; getSupplierDeliveryTime(productId, supplierIds)))\n            .toList();\n\n    scope.join();\n\n    <span class=\"hljs-keyword\">return<\/span> subtasks.stream()\n        .filter(subtask -&gt; subtask.state() == State.SUCCESS)\n        .map(Subtask::get)\n        .toList();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Und hier der entsprechende Code f\u00fcr Java 25 (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/SupplierDeliveryTimeCheck3_NestedStructuredTaskScope.java\" target=\"_blank\" rel=\"noopener\">SupplierDeliveryTimeCheck3_NestedStructuredTaskScope<\/a> im <code>main<\/code>-Branch des GitHub-Repos). Dieses Mal verwende ich <code>Joiner.allSuccessfulOrThrow()<\/code>, um von <code>scope.join()<\/code> direkt einen Stream der Subtasks geliefert zu bekommen \u2013 so muss ich mir die Subtasks nicht vorher merken.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\">List&lt;SupplierDeliveryTime&gt; <span class=\"hljs-title\">getSupplierDeliveryTimes<\/span><span class=\"hljs-params\">(\n    List&lt;String&gt; productIds, List&lt;String&gt; supplierIds)<\/span> <span class=\"hljs-keyword\">throws<\/span> InterruptedException <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope =\n      StructuredTaskScope.open(Joiner.&lt;SupplierDeliveryTime&gt;allSuccessfulOrThrow())) {\n    productIds.forEach(\n        productId -&gt; scope.fork(() -&gt; getSupplierDeliveryTime(productId, supplierIds)));\n\n    <span class=\"hljs-keyword\">return<\/span> scope.join().map(Subtask::get).toList();\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In beiden Beispielen erzeugen wir einen <code>StructuredTaskScope<\/code> \u2013 und innerhalb dieses Scopes forken wir Subtasks, die wiederum die im vorherigen Abschnitt gezeigte Methode <code>getSupplierDeliveryTime(...)<\/code> aufrufen, welche damit also innerhalb des Scopes von <code>getSupplierDeliveryTimes(...)<\/code> verschachtelte Scopes \u00f6ffnen.<\/p>\n\n\n\n<p>Die folgende Grafik zeigt diese Scopes als gestrichelte Linien:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"286\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-800x286.png\" alt=\"Verschachtelte StructuredTaskScopes\" class=\"wp-image-37249\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-800x286.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-224x80.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-336x120.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-504x180.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-672x240.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-400x143.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-600x215.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-944x337.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested-1200x429.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-nested.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Verschachtelte StructuredTaskScopes<\/figcaption><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"vorteile-von-structured-concurrency\">Vorteile von Structured Concurrency<\/h2>\n\n\n\n<p>Structured Concurrency zeichnet sich durch klar im Code ersichtliche Start- und Endpunkte nebenl\u00e4ufiger Subtasks aus. Fehler in den Subtasks werden an den Eltern-Scope propagiert. Das macht den Code besser les- und wartbar und stellt sicher, dass zum Ende eines Scopes alle gestarteten Threads beendet sind.<\/p>\n\n\n\n<p>Die folgende Grafik zeigt Unstructured und Structured Concurrency gegen\u00fcbergestellt:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"369\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-800x369.png\" alt=\"Unstructured Concurrency vs. Structured Concurrency\" class=\"wp-image-37255\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-800x369.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-224x103.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-336x155.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-504x232.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-672x310.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-400x185.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-600x277.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-944x435.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2-1200x554.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/structured-concurrency-vs-unstructured-concurrency.v2.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Unstructured Concurrency vs. Structured Concurrency<\/figcaption><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"vorteile-von-structuredtaskscope\">Vorteile von StructuredTaskScope<\/h2>\n\n\n\n<p>Mit <code>StructuredTaskScope<\/code> haben wir ein Sprachkonstrukt f\u00fcr <em>Structured Concurrency<\/em>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Task und Subtasks bilden im Code eine abgeschlossene Einheit \u2013 es gibt keinen <code>ExecutorService<\/code> in einem h\u00f6heren Scope, wie z. B. der Klasse. Die Threads kommen nicht aus einem Threadpool; stattdessen wird jeder Subtask in einem neuen <a href=\"\/de\/java\/virtual-threads\/\">virtuellen Thread<\/a> ausgef\u00fchrt.<\/li>\n\n\n\n<li>Durch den mittels <em>try-with-resources<\/em>-Block aufgespannten Scope ergeben sich klare Start- und Endpunkte aller Threads.<\/li>\n\n\n\n<li>Am Ende des Scopes sind alle Threads beendet.<\/li>\n\n\n\n<li>Fehler innerhalb der Subtasks werden sauber an den Eltern-Scope propagiert.<\/li>\n\n\n\n<li>Je nach Policy werden die verbleibenden Subtasks abgebrochen, wenn ein Subtasks erfolgreich war oder wenn in einem Subtask ein Fehler auftrat.<\/li>\n\n\n\n<li>Wenn der aufrufende Thread abgebrochen wird, werden auch die Subtasks abgebrochen.<\/li>\n<\/ul>\n\n\n\n<p>Dar\u00fcber hinaus hilft <code>StructuredTaskScope<\/code> beim Debuggen: Wenn wir einen Thread-Dump im neuen JSON-Format ausgeben (<code>jcmd &lt;pid&gt; Thread.dump_to_file -format=json &lt;file&gt;<\/code>), dann ist darin die Aufrufhierarchie zwischen Eltern- und Kind-Threads ersichtlich.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"structuredtaskscope-und-scoped-values\">StructuredTaskScope und Scoped Values<\/h2>\n\n\n\n<p>Die in <a href=\"\/de\/java\/java-20-features\/#Scoped_Values_Incubator_-_JEP_429\">Java 20<\/a> als Incubator, in <a href=\"\/de\/java\/java-21-features\/#Scoped_Values_Preview_-_JEP_446\">Java 21<\/a> als Preview eingef\u00fchrten und in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-25-features\/#scoped-values-jep-506\">Java 25<\/a> finalisierten <a href=\"\/de\/java\/scoped-values\/\">Scoped Values<\/a> werden beim Einsatz von <code>StructuredTaskScope<\/code> innerhalb eines Scopes automatisch an alle durch <code>StructuredTaskScope.fork(...)<\/code> erzeugten Kind-Threads weitervererbt.<\/p>\n\n\n\n<p>Wie das genau funktioniert, zeige ich dir an folgendem Code-Beispiel (Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/java-21\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/SupplierDeliveryTimeCheck4_NestedStructuredTaskScopeUsingScopedValue.java\" target=\"_blank\" rel=\"noopener\">SupplierDeliveryTimeCheck4_NestedStructuredTaskScopeUsingScopedValue<\/a> im <code>java-21<\/code>-Branch bzw. <a href=\"https:\/\/github.com\/SvenWoltmann\/structured-concurrency\/blob\/main\/src\/main\/java\/eu\/happycoders\/structuredconcurrency\/demo3_suppliers\/SupplierDeliveryTimeCheck4_NestedStructuredTaskScopeUsingScopedValue.java\" target=\"_blank\" rel=\"noopener\">SupplierDeliveryTimeCheck4_NestedStructuredTaskScopeUsingScopedValue<\/a> im <code>main<\/code>-Branch).<\/p>\n\n\n\n<p>Wir erzeugen ein <code>ScopedValue<\/code> \u2013 im Beispiel f\u00fcr einen API-Key, binden dieses an den API-Key und rufen dann die im Abschnitt \u201eVerschachtelte StructuredTaskScopes\u201d gezeigte Methode <code>getSupplierDeliveryTimes(...)<\/code> innerhalb des Scopes per <code>call()<\/code> auf:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> ScopedValue&lt;String&gt; API_KEY = ScopedValue.newInstance();\n\n<span class=\"hljs-function\">List&lt;SupplierDeliveryTime&gt; <span class=\"hljs-title\">getSupplierDeliveryTimes<\/span><span class=\"hljs-params\">(\n    List&lt;String&gt; productIds, List&lt;String&gt; supplierIds, String apiKey)<\/span> <span class=\"hljs-keyword\">throws<\/span> Exception <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> ScopedValue.where(API_KEY, apiKey)\n      .call(() -&gt; getSupplierDeliveryTimes(productIds, supplierIds));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Durch die Vererbung des Scoped Values <code>API_KEY<\/code> kann auf diesen auch innerhalb der <code>SupplierDeliveryTimeService.getDeliveryTime(...)<\/code>-Methode zugegriffen werden ohne ihn \u00fcber Methodenargumente an diese Methode durchschleifen zu m\u00fcssen \u2013 und das selbst dann, wenn die Methoden nicht in demjenigen Thread ausgef\u00fchrt werden, der <code>ScopedValue.where(...)<\/code> aufruft, sondern eben in den durch <code>StructuredTaskScope.fork(...)<\/code> erzeugten Kind- bzw. in diesem Beispiel sogar Enkel-Threads.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"fazit\">Fazit<\/h2>\n\n\n\n<p><em>Structured Concurrency<\/em> wird \u2013 aufbauend auf <a href=\"\/de\/java\/virtual-threads\/\">virtuelle Threads<\/a> \u2013 die Verwaltung von Tasks, die in nebenl\u00e4ufige Subtasks aufgeteilt werden, deutlich vereinfachen. Policies erlauben uns das Verhalten von <code>StructuredTaskScope<\/code> zu beeinflussen, z. B. um alle Tasks abzubrechen, sollte einer fehlgeschlagen sein.<\/p>\n\n\n\n<p>Die API wurde in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-25-features\/\">Java 25<\/a> grundlegend \u00fcberarbeitet: <code>StructuredTaskScope<\/code> und Join-Strategie wurden entkoppelt, was zu klarer strukturiertem, verst\u00e4ndlicherem und robusterem Code f\u00fchrt (Stichwort: <a href=\"https:\/\/de.wikipedia.org\/wiki\/Komposition_an_Stelle_von_Vererbung\" target=\"_blank\" rel=\"noopener\">Composition over inheritance<\/a>).<\/p>\n\n\n\n<p>Bitte beachte, dass sich <em>Structured Concurrency<\/em> nach wie vor im Preview-Stadium befindet und somit noch \u00c4nderungen unterliegen kann.<\/p>\n<aside><p>Wenn dir der Artikel weitergeholfen hat, w\u00fcrde ich mich sehr \u00fcber eine positive Bewertung auf meinem <a href=\"https:\/\/www.provenexpert.com\/de-de\/sven-woltmann-happycoders-eu\/7smk\/\" target=\"_blank\" rel=\"noopener\">ProvenExpert-Profil<\/a> freuen. Dein Feedback hilft mir, meine Inhalte weiter zu verbessern und motiviert mich, neue informative Artikel zu schreiben.<\/p>\r\n                        <p>\ud83d\udc49 <a href=\"https:\/\/www.provenexpert.com\/de-de\/sven-woltmann-happycoders-eu\/7smk\/\" target=\"_blank\" rel=\"noopener\">Bewertung abgeben<\/a><\/p>\r\n                        <p>Du m\u00f6chtest \u00fcber alle neue Java-Features auf dem Laufenden sein? Dann <a href=\"#\" data-formkit-toggle=\"d8ee997126\">klicke hier<\/a>, um dich f\u00fcr den HappyCoders-Newsletter anzumelden.<\/p>\r\n                        <p>\ud83d\udc49 <a href=\"#\" data-formkit-toggle=\"d8ee997126\">Newsletter-Anmeldung<\/a><\/p><\/aside>","protected":false},"excerpt":{"rendered":"<p>Was ist Structured Concurrency und wof\u00fcr ben\u00f6tigen wir sie? Wie funktioniert StructuredTaskScope? Was ist der Vorteil von Structured Concurrency?<\/p>\n","protected":false},"author":1,"featured_media":34140,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_titles_title":"","_seopress_titles_desc":"Was ist Structured Concurrency und wof\u00fcr ben\u00f6tigen wir sie? Wie funktioniert StructuredTaskScope? Was ist der Vorteil von Structured Concurrency?","_seopress_robots_index":"","_seopress_robots_follow":"","_seopress_robots_imageindex":"","_seopress_robots_snippet":"","_seopress_robots_primary_cat":"none","_seopress_robots_breadcrumbs":"","_seopress_robots_freeze_modified_date":"","_seopress_robots_custom_modified_date":"","_seopress_robots_canonical":"","_seopress_social_fb_title":"","_seopress_social_fb_desc":"","_seopress_social_fb_img":"","_seopress_social_fb_img_attachment_id":0,"_seopress_social_fb_img_width":0,"_seopress_social_fb_img_height":0,"_seopress_social_twitter_title":"","_seopress_social_twitter_desc":"","_seopress_social_twitter_img":"","_seopress_social_twitter_img_attachment_id":0,"_seopress_social_twitter_img_width":0,"_seopress_social_twitter_img_height":0,"_seopress_redirections_value":"","_seopress_redirections_enabled":"","_seopress_redirections_enabled_regex":"","_seopress_redirections_logged_status":"both","_seopress_redirections_param":"","_seopress_redirections_type":301,"_seopress_analysis_target_kw":"structured concurrency,structuredtaskscope","_seopress_news_disabled":"","_seopress_video_disabled":"","_seopress_video":[],"_seopress_pro_schemas_manual":[{"_seopress_pro_rich_snippets_type":"none"}],"_seopress_pro_rich_snippets_disable_all":"","_seopress_pro_rich_snippets_disable":[],"_seopress_pro_schemas":[],"_uag_custom_page_level_css":"","_wp_convertkit_post_meta":{"form":"-1","landing_page":"","tag":"0","restrict_content":"0"},"_metis_text_type":"standard","_metis_text_length":33184,"_post_count":0,"footnotes":""},"categories":[64],"tags":[220],"class_list":["post-34113","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-java","tag-java-nebenlaeufigkeit"],"uagb_featured_image_src":{"full":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",1770,986,false],"thumbnail":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",150,84,false],"medium":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",300,167,false],"medium_large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",768,428,false],"large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",1024,570,false],"feature_thumb_224":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-224x125.jpg",224,125,true],"feature_thumb_336":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-336x187.jpg",336,187,true],"feature_thumb_504":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-504x281.jpg",504,281,true],"feature_thumb_672":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-672x374.jpg",672,374,true],"half_400":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-400x223.jpg",400,223,true],"half_600":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-600x334.jpg",600,334,true],"full_800":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-800x446.jpg",800,446,true],"full_944":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-944x526.jpg",944,526,true],"full_1200":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-1200x668.jpg",1200,668,true],"wide_1180":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-1180x490.jpg",1180,490,true],"wide_1770":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java-1770x735.jpg",1770,735,true],"1536x1536":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",1536,856,false],"2048x2048":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/12\/structured-concurrency-java.jpg",1770,986,false]},"uagb_author_info":{"display_name":"Sven Woltmann","author_link":"https:\/\/www.happycoders.eu\/de\/author\/sven\/"},"uagb_comment_info":0,"uagb_excerpt":"Was ist Structured Concurrency und wof\u00fcr ben\u00f6tigen wir sie? Wie funktioniert StructuredTaskScope? Was ist der Vorteil von Structured Concurrency?","public_identification_id":"1f23a8ab4e1f4bdba5baabd231f0b8fd","private_identification_id":"18f33255e98d422a9745bb9dc7961fec","_links":{"self":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/34113","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/comments?post=34113"}],"version-history":[{"count":10,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/34113\/revisions"}],"predecessor-version":[{"id":53494,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/34113\/revisions\/53494"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media\/34140"}],"wp:attachment":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media?parent=34113"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/categories?post=34113"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/tags?post=34113"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}