{"id":31531,"date":"2022-06-13T19:30:00","date_gmt":"2022-06-13T17:30:00","guid":{"rendered":"https:\/\/www.happycoders.eu\/?p=31531"},"modified":"2026-06-12T08:57:09","modified_gmt":"2026-06-12T06:57:09","slug":"virtual-threads","status":"publish","type":"post","link":"https:\/\/www.happycoders.eu\/de\/java\/virtual-threads\/","title":{"rendered":"Virtuelle Threads in Java \u2013 Deep Dive mit Beispielen"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Virtuelle Threads (\u201evirtual threads\") sind eine der wichtigsten Neuerungen in Java seit langem. Sie wurden in <a href=\"https:\/\/openjdk.org\/projects\/loom\/\" target=\"_blank\" rel=\"noopener\">Project Loom<\/a> entwickelt und sind seit <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-19-features\/#virtual-threads-preview-jep-425\">Java 19<\/a> als Preview-Feature im JDK enthalten und seit <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-21-features\/#virtual-threads-jep-444\">Java 21<\/a> in ihrer finalen Version (<a href=\"https:\/\/openjdk.org\/jeps\/444\" target=\"_blank\" rel=\"noopener\">JEP 444<\/a>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem Artikel erf\u00e4hrst du:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Warum brauchen wir virtuelle Threads?<\/li>\n\n\n\n<li>Was sind virtuelle Threads, und wie funktionieren sie?<\/li>\n\n\n\n<li>Wie setzt man virtuelle Threads ein?<\/li>\n\n\n\n<li>Wie erzeugt man virtuelle Threads, und wie viele virtuelle Threads lassen sich starten?<\/li>\n\n\n\n<li>Wie verwendet man virtuelle Threads in Spring und Jakarta EE?<\/li>\n\n\n\n<li>Was sind die Vorteile von virtuellen Threads?<\/li>\n\n\n\n<li>Was sind virtuelle Threads nicht, und welche Einschr\u00e4nkungen weisen sie auf?<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Beginnen wir bei der Herausforderung, die zur Entwicklung virtueller Threads gef\u00fchrt hat.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"warum-brauchen-wir-virtuelle-threads\">Warum brauchen wir virtuelle Threads?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Wer jemals eine Backend-Anwendung unter hoher Last betreut hat, wei\u00df: Threads sind oft das Bottleneck. Denn f\u00fcr jeden eingehenden Request wird ein Thread ben\u00f6tigt, der den Request abarbeitet. Ein Java-Thread entspricht einem Betriebssystem-Thread, und diese sind ressourcenhungrig:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ein Betriebssystem-Thread reserviert 1 MB f\u00fcr den Stack im Voraus und committet 32 oder 64 KB davon, je nach Betriebssystem.<\/li>\n\n\n\n<li>Es dauert etwa 1 ms, um einen Betriebssystem-Thread zu starten.<\/li>\n\n\n\n<li>Context-Switches finden im Kernel-Space statt und sind recht CPU-intensiv.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Mehr als ein paar Tausend sollte man also nicht starten, sonst riskiert man die Stabilit\u00e4t des gesamten Systems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein paar Tausend reichen jedoch nicht immer \u2013 insbesondere dann nicht, wenn die Bearbeitung eines Requests l\u00e4nger dauert, weil auf blockierende Datenstrukturen, wie z. B. Queues, Locks, oder externe Dienste wie Datenbanken, Microservices oder Cloud-APIs gewartet werden muss.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn ein Request beispielsweise zwei Sekunden dauert und wir den Thread-Pool auf 1.000 Threads limitieren, dann k\u00f6nnten maximal 500 Anfragen pro Sekunde beantwortet werden. Die CPU w\u00e4re aber bei weitem nicht ausgelastet, da sie die meiste Zeit auf die Antworten der externen Dienste warten w\u00fcrde, selbst wenn pro CPU-Core mehrere Threads bedient werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bislang konnten wir dieses Problem nur mit asynchroner Programmierung bew\u00e4ltigen \u2013 z. B. mit <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/21\/docs\/api\/java.base\/java\/util\/concurrent\/CompletableFuture.html\" target=\"_blank\" rel=\"noopener\">CompletableFuture<\/a> oder reaktiven Frameworks wie <a href=\"https:\/\/github.com\/ReactiveX\/RxJava\" target=\"_blank\" rel=\"noopener\">RxJava<\/a> und <a href=\"https:\/\/projectreactor.io\/\" target=\"_blank\" rel=\"noopener\">Project Reactor<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wer allerdings schon einmal Code wie den folgenden warten musste, wei\u00df, dass asynchroner Code um ein Vielfaches komplexer ist als sequentieller Code \u2013 und absolut keinen Spa\u00df macht.<\/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\"><span class=\"hljs-keyword\">public<\/span> CompletionStage&lt;Response&gt; <span class=\"hljs-title\">getProduct<\/span><span class=\"hljs-params\">(String productId)<\/span> <\/span>{\n   <span class=\"hljs-keyword\">return<\/span> productService\n         .getProductAsync(productId)\n         .thenCompose(\n            product -&gt; {\n               <span class=\"hljs-keyword\">if<\/span> (product.isEmpty()) {\n                  <span class=\"hljs-keyword\">return<\/span> CompletableFuture.completedFuture(\n                        Response.status(Status.NOT_FOUND).build());\n               }\n\n               <span class=\"hljs-keyword\">return<\/span> warehouseService\n                     .isAvailableAsync(productId)\n                     .thenCompose(\n                           available -&gt;\n                                 available\n                                       ? CompletableFuture.completedFuture(<span class=\"hljs-number\">0<\/span>)\n                                       : supplierService.getDeliveryTimeAsync(\n                                             product.get().supplier(), productId))\n                     .thenApply(\n                           daysUntilShippable -&gt;\n                                 Response.ok(\n                                       <span class=\"hljs-keyword\">new<\/span> ProductPageResponse(\n                                             product.get(), daysUntilShippable))\n                                       .build());\n               });\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 class=\"wp-block-paragraph\">Nicht nur, dass dieser Code kaum lesbar ist, er ist auch extrem schwer zu debuggen. Beispielsweise w\u00fcrde es hier keinen Sinn machen einen Breakpoint zu setzen, denn der Code <em>definiert<\/em> nur den asynchronen Flow, f\u00fchrt ihn aber nicht aus. Ausgef\u00fchrt wird der eigentliche Business Code erst zu einem sp\u00e4teren Zeitpunkt in einem daf\u00fcr vorgesehenen Thread-Pool.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dar\u00fcber hinaus m\u00fcssen die eingesetzten Datenbanktreiber und Treiber f\u00fcr andere externe Dienste das asynchrone, nicht-blockierende Modell ebenso unterst\u00fctzen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"was-sind-virtuelle-threads\">Was sind virtuelle Threads?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Virtuelle Threads l\u00f6sen das Problem auf eine Art und Weise, die es uns wieder erlaubt, leicht lesbaren und wartbaren Code zu schreiben. Virtuelle Threads f\u00fchlen sich aus Sicht des Java-Codes wie ganz normale Threads an, werden aber nicht 1:1 auf Betriebssystem-Threads gemappt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Stattdessen gibt es einen Pool sogenannter Tr\u00e4ger-Threads (Carrier Threads), auf die ein virtueller Thread vor\u00fcbergehend gemappt wird (englisch: \u201emounted\u201c). Sobald der virtuelle Thread auf eine blockierende Operation st\u00f6\u00dft, wird der virtuelle Thread vom Tr\u00e4ger-Thread genommen (englisch: \u201eunmounted\u201c), und der Tr\u00e4ger-Thread kann einen anderen virtuellen Thread (einen neuen oder einen zuvor blockierten) ausf\u00fchren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die folgende Grafik stellt diese M:N-Zuordnung von virtuellen Threads zu Tr\u00e4ger-Threads und damit zu Betriebssystem-Threads dar:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"359\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-800x359.png\" alt=\"Mapping von virtuellen Threads auf Carrier-Threads auf Betriebssystem-Threads\" class=\"wp-image-37339\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-800x359.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-224x101.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-336x151.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-504x226.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-672x302.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-400x180.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-600x269.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-944x424.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads-1200x539.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/virtual-threads-mapped-to-carrier-threads.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Mapping von virtuellen Threads auf Carrier-Threads auf Betriebssystem-Threads<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Beim Tr\u00e4ger-Thread-Pool handelt es sich um einen <code>ForkJoinPool<\/code> \u2013 also einen Pool, bei dem jeder Thread seine eigene Queue hat und Tasks von den Queues anderer Threads \u201estiehlt\u201c, sollte seine eigene Queue leer sein. Seine Gr\u00f6\u00dfe wird standardm\u00e4\u00dfig auf <code>Runtime.getRuntime().availableProcessors()<\/code> gesetzt und kann mit der VM-Option <code>jdk.virtualThreadScheduler.parallelism<\/code> angepasst werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Im zeitlichen Verlauf k\u00f6nnte beispielsweise die CPU-Aktivit\u00e4t von drei Tasks, die jeweils viermal Code ausf\u00fchren und dazwischen dreimal verh\u00e4ltnism\u00e4\u00dfig lange blockieren, wie folgt auf einen einzigen Carrier-Thread gemappt werden:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img decoding=\"async\" width=\"1600\" height=\"776\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread.png\" alt=\"multiple virtual threads mapped to one carrier thread\" class=\"wp-image-37340\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread.png 1600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-224x109.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-336x163.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-504x244.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-672x326.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-400x194.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-600x291.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-800x388.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-944x458.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/09\/multiple-virtual-threads-mapped-to-one-carrier-thread-1200x582.png 1200w\" sizes=\"(max-width: 1600px) 100vw, 1600px\" \/><figcaption class=\"wp-element-caption\">Mapping von drei virtuellen Threads auf einen Carrier-Thread<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Blockierende Operationen blockieren somit den ausf\u00fchrenden Tr\u00e4ger-Thread nicht, und wir k\u00f6nnen mit einem kleinen Pool von Tr\u00e4ger-Threads eine Vielzahl von Requests parallel bearbeiten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Den Beispiel-Use-Case von oben k\u00f6nnte man dann ganz einfach mit sequentiellem, blockierenden Code wie folgt implementieren:<\/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\"><span class=\"hljs-keyword\">public<\/span> ProductPageResponse <span class=\"hljs-title\">getProduct<\/span><span class=\"hljs-params\">(String productId)<\/span> <\/span>{\n    Product product = productService.getProduct(productId)\n            .orElseThrow(NotFoundException::<span class=\"hljs-keyword\">new<\/span>);\n\n    <span class=\"hljs-keyword\">boolean<\/span> available = warehouseService.isAvailable(productId);\n\n    <span class=\"hljs-keyword\">int<\/span> shipsInDays =\n        available ? <span class=\"hljs-number\">0<\/span> : supplierService.getDeliveryTime(product.supplier(), productId);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ProductPageResponse(product, shipsInDays);\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 class=\"wp-block-paragraph\">Dieser Code ist nicht nur einfacher zu schreiben und zu lesen, sondern auch \u2013 wie jeder sequentielle Code \u2013 mit herk\u00f6mmlichen Mitteln zu debuggen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Falls dein Code bereits so aussieht \u2013 du also nie auf asynchrone Programmierung umgestellt hast, dann habe ich gute Nachrichten: du kannst deinen Code unver\u00e4ndert mit virtuellen Threads weiterverwenden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"virtuelle-threads-beispiel\">Virtuelle Threads \u2013 Beispiel<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die M\u00e4chtigkeit virtueller Threads k\u00f6nnen wir auch ohne Backend-Framework demonstrieren. Dazu simulieren wir ein Szenario, das dem oben beschriebenen \u00e4hnelt: wir starten 1.000 Tasks, die jeweils eine Sekunde warten (um den Zugriff auf eine externe API zu simulieren) und dann ein Ergebnis (im Beispiel eine Zufallszahl) zur\u00fcckliefern.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Als erstes implementieren wir den Task:<\/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-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Task<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">Callable<\/span>&lt;<span class=\"hljs-title\">Integer<\/span>&gt; <\/span>{\n\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-keyword\">int<\/span> number;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">Task<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> number)<\/span> <\/span>{\n        <span class=\"hljs-keyword\">this<\/span>.number = number;\n    }\n\n    <span class=\"hljs-meta\">@Override<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Integer <span class=\"hljs-title\">call<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n        System.out.printf(<span class=\"hljs-string\">\"Thread %s - Task %d waiting...%n\"<\/span>, \n                Thread.currentThread().getName(), number);\n\n        <span class=\"hljs-keyword\">try<\/span> {\n            Thread.sleep(<span class=\"hljs-number\">1000<\/span>);\n        } <span class=\"hljs-keyword\">catch<\/span> (InterruptedException e) {\n            System.out.printf(<span class=\"hljs-string\">\"Thread %s - Task %d canceled.%n\"<\/span>, \n                    Thread.currentThread().getName(), number);\n            <span class=\"hljs-keyword\">return<\/span> -<span class=\"hljs-number\">1<\/span>;\n        }\n\n        System.out.printf(<span class=\"hljs-string\">\"Thread %s - Task %d finished.%n\"<\/span>, \n                Thread.currentThread().getName(), number);\n        <span class=\"hljs-keyword\">return<\/span> ThreadLocalRandom.current().nextInt(<span class=\"hljs-number\">100<\/span>);\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 class=\"wp-block-paragraph\">Nun messen wir, wie lange es mit einem Pool von 100 Plattform-Threads (so werden <em>nicht<\/em> virtuelle Threads bezeichnet) dauert, alle 1.000 Tasks abzuarbeiten:<\/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\"><span class=\"hljs-keyword\">try<\/span> (ExecutorService executor = Executors.newFixedThreadPool(<span class=\"hljs-number\">100<\/span>)) {\n    List&lt;Task&gt; tasks = <span class=\"hljs-keyword\">new<\/span> ArrayList&lt;&gt;();\n    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">int<\/span> i = <span class=\"hljs-number\">0<\/span>; i &lt; <span class=\"hljs-number\">1_000<\/span>; i++) {\n        tasks.add(<span class=\"hljs-keyword\">new<\/span> Task(i));\n    }\n\n    <span class=\"hljs-keyword\">long<\/span> time = System.currentTimeMillis();\n\n    List&lt;Future&lt;Integer&gt;&gt; futures = executor.invokeAll(tasks);\n\n    <span class=\"hljs-keyword\">long<\/span> sum = <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-keyword\">for<\/span> (Future&lt;Integer&gt; future : futures) {\n        sum += future.get();\n    }\n\n    time = System.currentTimeMillis() - time;\n\n    System.out.println(<span class=\"hljs-string\">\"sum = \"<\/span> + sum + <span class=\"hljs-string\">\"; time = \"<\/span> + time + <span class=\"hljs-string\">\" ms\"<\/span>);\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<div class=\"wp-block-uagb-container uagb-block-8ae4ca2e alignfull uagb-is-root-container\">\n<div class=\"uagb-block-b769b372 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><code>ExecutorService<\/code> ist \u00fcbrigens seit Java 19 auto-closeable, d. h. er kann durch einen try-with-resources-Block umschlossen sein. Am Ende des Blocks wird <code>ExecutorService.close()<\/code> aufgerufen, was wiederum <code>shutdown()<\/code> und <code>awaitTermination()<\/code> aufruft \u2013 und ggf. <code>shutdownNow()<\/code>, wenn der Thread w\u00e4hrend <code>awaitTermination()<\/code> interrupted wird.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Das Programm l\u00e4uft etwas \u00fcber 10 Sekunden. Das war zu erwarten:<\/p>\n\n\n\n<p class=\"has-text-align-center wp-block-paragraph\">1.000 Tasks geteilt durch 100 Threads = 10 Tasks pro Thread<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Jeder Plattform-Thread musste 10 Tasks, die jeweils etwa 1 Sekunde dauerten, sequentiell abarbeiten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Als n\u00e4chstes testen wir das ganze mit virtuellen Threads. Dazu m\u00fcssen wir lediglich den Ausdruck<\/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\">Executors.newFixedThreadPool(<span class=\"hljs-number\">100<\/span>)<\/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 class=\"wp-block-paragraph\">ersetzen durch:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Executors.newVirtualThreadPerTaskExecutor()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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 class=\"wp-block-paragraph\">Dieser Executor verwendet keinen Thread-Pool, sondern legt f\u00fcr jeden Task einen neuen virtuellen Thread an.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Danach ben\u00f6tigt das Programm keine 10 Sekunden mehr, sondern nur noch knapp \u00fcber eine Sekunde. Schneller geht es auch kaum, da ja jeder Task eine Sekunde wartet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beeindruckend: selbst 10.000 Tasks kann unser kleines Programm in etwas \u00fcber einer Sekunde abarbeiten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Erst bei 100.000 Tasks l\u00e4sst der Durchsatz sp\u00fcrbar nach: hierf\u00fcr ben\u00f6tigt mein Laptop etwa vier Sekunden \u2013 was aber immer noch rasend schnell ist im Vergleich zum Thread-Pool, der daf\u00fcr knapp 17 Minuten brauchen w\u00fcrde.<\/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=\"wie-erzeugt-man-virtuelle-threads\">Wie erzeugt man virtuelle Threads?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Eine M\u00f6glichkeit zur Erzeugung virtueller Threads haben wir bereits kennengelernt: Ein Executor Service, den wir mit <code>Executors.newVirtualThreadPerTaskExecutor()<\/code> erzeugen, erstellt pro Task einen neuen virtuellen Thread.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mittels <code>Thread.startVirtualThread()<\/code> oder <code>Thread.ofVirtual().start()<\/code> k\u00f6nnen wir virtuelle Threads auch explizit starten:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Thread.startVirtualThread(() -&gt; {\n    <span class=\"hljs-comment\">\/\/ code to run in thread<\/span>\n});\n\nThread.ofVirtual().start(() -&gt; {\n    <span class=\"hljs-comment\">\/\/ code to run in thread<\/span>\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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 class=\"wp-block-paragraph\">Bei der zweiten Variante liefert <code>Thread.ofVirtual()<\/code> einen <code>Thread.Builder.OfVirtual<\/code> zur\u00fcck, dessen <code>start()<\/code>-Methode einen virtuellen Thread startet. Die alternative Methode <code>Thread.ofPlatform()<\/code> liefert einen <code>Thread.Builder.OfPlatform<\/code> zur\u00fcck, \u00fcber den ein Plattform-Thread gestartet werden kann.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beide sind Subinterfaces von <code>Thread.Builder<\/code>. Das erm\u00f6glicht uns flexiblen Code zu schreiben, bei dem erst zur Laufzeit entschieden wird, ob dieser in einem virtuellen oder in einem Plattform-Thread laufen soll:<\/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\">Thread.Builder threadBuilder = createThreadBuilder();\nthreadBuilder.start(() -&gt; {\n    <span class=\"hljs-comment\">\/\/ code to run in thread<\/span>\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 class=\"wp-block-paragraph\">Herauszufinden, ob Code in einem virtuellen Thread l\u00e4uft, kannst du \u00fcbrigens mit <code>Thread.currentThread().isVirtual()<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wie-viele-virtuelle-threads-koennen-gestartet-werden\">Wie viele virtuelle Threads k\u00f6nnen gestartet werden?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem <a href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads\" target=\"_blank\" rel=\"noopener\">GitHub-Repository<\/a> findest du zahlreiche Demo-Programme, die die F\u00e4higkeiten von virtuellen Threads demonstrieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mit der Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads\/blob\/main\/src\/main\/java\/eu\/happycoders\/virtualthreads\/presentation\/HowManyVirtualThreadsDoingSomething.java\" data-type=\"link\" data-id=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads\/blob\/main\/src\/main\/java\/eu\/happycoders\/virtualthreads\/presentation\/HowManyVirtualThreadsDoingSomething.java\" target=\"_blank\" rel=\"noopener\">HowManyVirtualThreadsDoingSomething<\/a> kannst du testen, wie viele virtuelle Threads du auf deinem System laufen lassen kannst. Die Anwendung startet mehr und mehr Threads und f\u00fchrt in diesen Threads <code>Thread.sleep()<\/code>-Operationen in einer Endlosschleife durch, um das Warten auf die Antwort von einer Datenbank oder einer externen API zu simulieren. Versuche dem Programm mit der VM-Option <code>-Xmx<\/code> so viel Heap-Speicher wie m\u00f6glich zur Verf\u00fcgung zu stellen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auf meinem 64-GB-Rechner lie\u00dfen sich problemlos 20.000.000 virtuelle Threads starten \u2013 und mit etwas Geduld auch 30.000.000. Ab dann versuchte der Garbage Collector pausenlos Full GCs durchzuf\u00fchren \u2013 denn der Stack von virtuellen Threads wird auf dem Heap, in sogenannten <code>StackChunk<\/code>-Objekten \u201egeparkt\u201c, sobald ein virtueller Thread blockiert. Kurz darauf beendete sich die Anwendung mit einem <code>OutOfMemoryError<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mit der Klasse <a href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads\/blob\/main\/src\/main\/java\/eu\/happycoders\/virtualthreads\/presentation\/HowManyPlatformThreadsDoingSomething.java\" target=\"_blank\" rel=\"noopener\">HowManyPlatformThreadsDoingSomething<\/a> kannst du au\u00dferdem testen, wie viele Plattform-Threads dein System unterst\u00fctzt. Doch sei gewarnt: Meistens endet das Programm irgendwann mit einem <code>OutOfMemoryError<\/code> (bei mir zwischen 80.000 und 90.000 Threads) \u2013 es kann aber auch deinen Rechner zum Absturz bringen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wie-verwendet-man-virtuelle-threads-mit-jakarta-ee\">Wie verwendet man virtuelle Threads mit Jakarta EE?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In einer Jakarta-EE-Anwendung erzeugst du Threads nicht selbst \u2013 das \u00fcberl\u00e4sst du dem Container. Seit <a href=\"https:\/\/jakarta.ee\/release\/11\/\" target=\"_blank\" rel=\"noopener\">Jakarta EE 11<\/a> (<a href=\"https:\/\/jakarta.ee\/specifications\/concurrency\/3.1\/\" target=\"_blank\" rel=\"noopener\">Jakarta Concurrency 3.1<\/a>) kannst du anfordern, dass ein managed Executor oder eine managed Thread-Factory virtuelle Threads verwendet. Dazu setzt du das Attribut <code>virtual = true<\/code>, z. B. an einer <code>@ManagedExecutorDefinition<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@ManagedExecutorDefinition<\/span>(\n        name = <span class=\"hljs-string\">\"java:app\/concurrent\/virtualExecutor\"<\/span>,\n        virtual = <span class=\"hljs-keyword\">true<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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 class=\"wp-block-paragraph\">Das gleiche Attribut gibt es auch bei <code>@ManagedScheduledExecutorDefinition<\/code> und <code>@ManagedThreadFactoryDefinition<\/code>. Den so definierten Executor injizierst du anschlie\u00dfend und f\u00fchrst deine Tasks dar\u00fcber aus. Standardm\u00e4\u00dfig ist <code>virtual<\/code> auf <code>false<\/code> gesetzt \u2013 der Container erzeugt also weiterhin Plattform-Threads, solange du virtuelle Threads nicht explizit aktivierst.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Einen einzelnen REST-Endpoint kannst du im Standard von Jakarta EE (Stand Jakarta EE 11) allerdings nicht per Annotation direkt auf einen virtuellen Thread legen. Genau das bietet aber das Quarkus-\/SmallRye-\u00d6kosystem mit der Annotation <code>@RunOnVirtualThread<\/code> \u2013 dazu mehr im n\u00e4chsten Abschnitt.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-8f53d1a1 alignfull uagb-is-root-container\">\n<div class=\"uagb-block-74d9b74a uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><strong>Ausblick: Jakarta EE 12<br><\/strong><br>Mit <a href=\"https:\/\/jakarta.ee\/specifications\/platform\/12\/\" target=\"_blank\" rel=\"noopener\">Jakarta EE 12<\/a> soll die Annotation <code>@RunOnVirtualThread<\/code> Teil des Standards werden. Der Release verz\u00f6gert sich jedoch: Urspr\u00fcnglich f\u00fcr Mitte 2026 geplant, peilt das Platform-Team <a href=\"https:\/\/www.agilejava.eu\/2026\/02\/15\/hashtag-jakarta-ee-320\/\" target=\"_blank\" rel=\"noopener\">laut Ivar Grimstad<\/a> (Jakarta-EE-Developer-Advocate) inzwischen das Jakarta EE Core Profile f\u00fcr Q4 2026 sowie Web Profile und Platform f\u00fcr Q1\/Q2 2027 an.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wie-verwendet-man-virtuelle-threads-mit-quarkus\">Wie verwendet man virtuelle Threads mit Quarkus?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/quarkus.io\/\" data-type=\"link\" data-id=\"https:\/\/quarkus.io\/\" target=\"_blank\" rel=\"noopener\">Quarkus<\/a> bietet die Annotation <code>@RunOnVirtualThread<\/code> an. Sie stammt aus SmallRye Common (<code>io.smallrye.common.annotation.RunOnVirtualThread<\/code>) und ist \u2013 Stand heute \u2013 kein Teil der Jakarta-EE-Spezifikation. Mit ihr legst du einen einzelnen Endpoint auf einen virtuellen Thread:<\/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-meta\">@GET<\/span>\n<span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/product\/{productId}\"<\/span>)\n<span class=\"hljs-meta\">@RunOnVirtualThread<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> ProductPageResponse <span class=\"hljs-title\">getProduct<\/span><span class=\"hljs-params\">(@PathParam(<span class=\"hljs-string\">\"productId\"<\/span>)<\/span> String productId) <\/span>{\n    Product product = productService.getProduct(productId)\n            .orElseThrow(NotFoundException::<span class=\"hljs-keyword\">new<\/span>);\n\n    <span class=\"hljs-keyword\">boolean<\/span> available = warehouseService.isAvailable(productId);\n\n    <span class=\"hljs-keyword\">int<\/span> shipsInDays =\n        available ? <span class=\"hljs-number\">0<\/span> : supplierService.getDeliveryTime(product.supplier(), productId);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ProductPageResponse(product, shipsInDays);\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 class=\"wp-block-paragraph\">Am Methodenk\u00f6rper musst du nicht ein einziges Zeichen \u00e4ndern. Quarkus unterst\u00fctzt <code>@RunOnVirtualThread<\/code> bereits seit <a href=\"https:\/\/quarkus.io\/blog\/quarkus-2-10-0-final-released\/\" target=\"_blank\" rel=\"noopener\">Version 2.10<\/a> \u2013 also seit Juni 2022.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem <a href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads-quarkus\" target=\"_blank\" rel=\"noopener\">GitHub-Repository<\/a> findest du eine Beispiel-Quarkus-Anwendung mit dem oben gezeigten Controller \u2013 einmal mit Plattform-Threads, einmal mit virtuellen Threads und au\u00dferdem eine asynchrone Variante mit <code>CompletableFuture<\/code>. Die README erkl\u00e4rt, wie du die Anwendung startest und wie du die drei Controller aufrufst.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wie-verwendet-man-virtuelle-threads-mit-spring\">Wie verwendet man virtuelle Threads mit Spring?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In Spring w\u00fcrde der Controller wie folgt aussehen:<\/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-meta\">@GetMapping<\/span>(<span class=\"hljs-string\">\"\/stage1-seq\/product\/{productId}\"<\/span>)\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> ProductPageResponse <span class=\"hljs-title\">getProduct<\/span><span class=\"hljs-params\">(@PathVariable(<span class=\"hljs-string\">\"productId\"<\/span>)<\/span> String productId) <\/span>{\n    Product product = productService\n            .getProduct(productId)\n            .orElseThrow(() -&gt; <span class=\"hljs-keyword\">new<\/span> ResponseStatusException(NOT_FOUND));\n\n    <span class=\"hljs-keyword\">boolean<\/span> available = warehouseService.isAvailable(productId);\n\n    <span class=\"hljs-keyword\">int<\/span> shipsInDays =\n            available ? <span class=\"hljs-number\">0<\/span> : supplierService.getDeliveryTime(product.supplier(), productId);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> ProductPageResponse(product, shipsInDays);\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 class=\"wp-block-paragraph\">Seit Spring Boot 3.2 gen\u00fcgt eine einzige Property in der <code>application.properties<\/code>, um alle Request-Handler auf virtuellen Threads laufen zu lassen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Properties\" data-shcb-language-slug=\"properties\"><span><code class=\"hljs language-properties\"><span class=\"hljs-meta\">spring.threads.virtual.enabled<\/span>=<span class=\"hljs-string\">true<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Properties<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">properties<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Spring Boot konfiguriert daraufhin u. a. den Webserver (Tomcat\/Jetty) und den Task-Executor so, dass jeder Request in einem eigenen virtuellen Thread bearbeitet wird.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beachte: Damit laufen <em>alle<\/em> Controller auf virtuellen Threads. F\u00fcr die meisten Anwendungsf\u00e4lle ist das in Ordnung \u2013 nicht jedoch bei CPU-lastigen Aufgaben, die weiterhin auf Plattform-Threads laufen sollten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem <a href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads-spring\" target=\"_blank\" rel=\"noopener\">GitHub-Repository<\/a> findest du eine Beispiel-Spring-Anwendung mit dem oben gezeigten Controller. Die README erkl\u00e4rt, wie du die Anwendung startest und wie du den Controller von Plattform-Threads auf virtuelle Threads umschaltest.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-c548f5bf alignfull uagb-is-root-container\">\n<div class=\"uagb-block-01d3f74f uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><strong>Manuelle Konfiguration (vor Spring Boot 3.2)<\/strong><br><br>Vor Spring Boot 3.2 \u2013 oder wenn du das Verhalten feiner steuern willst \u2013 konfigurierst du virtuelle Threads \u00fcber zwei Beans:<br><br><code>@Bean(APPLICATION_TASK_EXECUTOR_BEAN_NAME)<br>public AsyncTaskExecutor asyncTaskExecutor() {<br>&nbsp; &nbsp; return new TaskExecutorAdapter(<br>&nbsp; &nbsp; &nbsp; &nbsp; Executors.newVirtualThreadPerTaskExecutor());<br>}<br><br>@Bean<br>public TomcatProtocolHandlerCustomizer&lt;?&gt; protocolHandlerVirtualThreadExecutorCustomizer() {<br>&nbsp; &nbsp; return protocolHandler -&gt; {<br>&nbsp; &nbsp; &nbsp; &nbsp; protocolHandler.setExecutor(<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Executors.newVirtualThreadPerTaskExecutor());<br>&nbsp; &nbsp; };<br>}<\/code><\/p><\/div><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"vorteile-von-virtuellen-threads\">Vorteile von virtuellen Threads<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Virtuelle Threads bieten beeindruckende Vorteile:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Erstens, sie sind g\u00fcnstig:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sie k\u00f6nnen deutlich schneller erzeugt werden als Plattform-Threads: die Erzeugung eines Plattform-Threads dauert etwa 1 ms, die Erzeugung eines virtuellen Threads weniger als 1 \u00b5s.<\/li>\n\n\n\n<li>Sie ben\u00f6tigen weniger Speicher: ein Plattform-Thread reserviert 1 MB f\u00fcr den Stack und committet, je nach Betriebssystem, 32 bis 64 KB im Voraus. Ein virtueller Thread beginnt mit etwa einem KB. Das gilt jedoch nur f\u00fcr flache Call-Stacks. Ein Call-Stack von der Gr\u00f6\u00dfe eines halben Megabytes ben\u00f6tigt dieses halbe Megabyte in beiden Thread-Varianten.<\/li>\n\n\n\n<li>Virtuelle Threads zu blockieren ist billig, da ein blockierter virtueller Thread keinen Betriebssystem-Thread blockiert. Umsonst ist es jedoch nicht, da der Stack des virtuellen Threads auf den Heap kopiert werden muss.<\/li>\n\n\n\n<li>Context Switches sind schnell, da sie im User Space, nicht im Kernel Space durchgef\u00fchrt werden und in der JVM zahlreiche Optimierungen vorgenommen wurden, um sie schneller zu machen.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Zweitens, wir k\u00f6nnen virtuelle Threads auf vertraute Weise einsetzen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An den <code>Thread<\/code>- und <code>ExecutorService<\/code>-APIs wurden nur minimale \u00c4nderungen vorgenommen.<\/li>\n\n\n\n<li>Anstatt asynchronen Code mit Callbacks zu schreiben, k\u00f6nnen wir Code im traditionellen blockierenden Thread-pro-Request-Stil schreiben.<\/li>\n\n\n\n<li>Wir k\u00f6nnen virtuelle Threads mit existierenden Tools debuggen, beobachten und profilen.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"was-sind-virtuelle-threads-nicht\">Was sind virtuelle Threads nicht?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Virtuelle Threads haben nat\u00fcrlich nicht nur Vorteile. Schauen wir uns zun\u00e4chst einmal an, was virtuelle Threads nicht sind, bzw. was wir mit ihnen nicht machen k\u00f6nnen oder sollten:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Virtuelle Threads sind keine schnelleren Threads \u2013 sie k\u00f6nnen nicht mehr CPU-Befehle als ein Plattform-Thread in derselben Zeit ausf\u00fchren. Sofern ein Task nicht blockiert, l\u00e4uft er auf einem virtuellen Thread aufgrund des Overheads des Mountens\/Unmountens sogar langsamer als auf einem bestehenden Plattform-Thread aus einem <code>ExecutorService<\/code>.<\/li>\n\n\n\n<li>Sie sind nicht pr\u00e4emptiv: W\u00e4hrend ein virtueller Thread eine CPU-intensive Aufgabe ausf\u00fchrt, wird er nicht vom Carrier-Thread genommen. Wenn du also 20 Carrier-Threads und 20 virtuelle Threads hast, die die CPU beanspruchen, ohne zu blockieren, wird kein anderer virtueller Thread ausgef\u00fchrt.<\/li>\n\n\n\n<li>Sie bieten keine h\u00f6here Abstraktion als Plattform-Threads. Du musst dir all der subtilen Dinge bewusst sein, die du auch bei der Verwendung regul\u00e4rer Threads beachten musst. Das hei\u00dft, wenn ein virtueller Thread auf gemeinsam genutzte Daten zugreift, musst du dich um Sichtbarkeitsprobleme k\u00fcmmern, du musst atomare Operationen synchronisieren usw.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"welche-einschraenkungen-weisen-virtuelle-threads-auf\">Welche Einschr\u00e4nkungen weisen virtuelle Threads auf?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u00dcber folgende Einschr\u00e4nkungen solltest du Bescheid wissen. Das Bild hat sich seit der Einf\u00fchrung in Java 21 allerdings deutlich entspannt \u2013 einige fr\u00fchere Einschr\u00e4nkungen sind inzwischen aufgehoben.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"1-nicht-unterstuetzte-blockierende-operationen\">1. Nicht unterst\u00fctzte blockierende Operationen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Die \u00fcberwiegende Mehrheit der blockierenden Operationen im JDK wurde so umgeschrieben, dass sie virtuelle Threads unterst\u00fctzt. Eine relevante Ausnahme bleibt (Stand <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-26-features\/\">Java 26<\/a>):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>File I\/O<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Hier blockiert ein blockierter virtueller Thread auch den Tr\u00e4ger-Thread. Um das zu kompensieren, erh\u00f6ht die JVM vor\u00fcbergehend die Anzahl der Tr\u00e4ger-Threads \u2013 bis zu einem Maximum von 256, das \u00fcber die VM-Option <code>jdk.virtualThreadScheduler.maxPoolSize<\/code> angepasst werden kann. An einer L\u00f6sung (u. a. auf Basis von io_uring) wird gearbeitet.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-5a15b66c alignfull uagb-is-root-container\">\n<div class=\"uagb-block-95aa6a85 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><strong>Verhalten in fr\u00fcheren Java-Versionen<\/strong><br><br>Bis Java 23 nahm auch <code>Object.wait()<\/code> einen virtuellen Thread nicht vom Tr\u00e4ger-Thread, sodass dieser ebenfalls blockierte. Seit <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-24-features\/#synchronize-virtual-threads-without-pinning-jep-491\">Java 24<\/a> (<a href=\"https:\/\/openjdk.org\/jeps\/491\" target=\"_blank\" rel=\"noopener\">JEP 491<\/a>) ist das behoben \u2013 <code>Object.wait()<\/code> gibt den Tr\u00e4ger-Thread jetzt frei.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"2-pinning\">2. Pinning<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pinning bedeutet, dass eine blockierende Operation, die normalerweise einen virtuellen Thread vom Tr\u00e4ger-Thread nehmen w\u00fcrde, dies nicht tut, weil der virtuelle Thread an seinen Tr\u00e4ger-Thread \u201egepinnt\u201c wurde \u2013 was bedeutet, dass er den Tr\u00e4ger-Thread nicht wechseln darf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Stand Java 26 tritt Pinning nur noch dann auf, wenn der Call-Stack Aufrufe nativen Codes enth\u00e4lt \u2013 denn was innerhalb von nativem Code passiert, k\u00f6nnen wir nicht kontrollieren. Innerhalb eines <code>synchronized<\/code>-Blocks pinnt ein virtueller Thread dagegen nicht mehr.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-3c562cc8 alignfull uagb-is-root-container\">\n<div class=\"uagb-block-51ec1b59 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><strong>Verhalten in fr\u00fcheren Java-Versionen<\/strong><br><br>Bis Java 23 pinnte auch ein <code>synchronized<\/code>-Block den virtuellen Thread. Daf\u00fcr gab es zwei Gr\u00fcnde:<br><br><strong>Grund 1: Zeiger auf Speicheradressen auf dem Stack.<\/strong> Beim sogenannten \u201eLegacy Stack Locking\u201c (dem Standard-Locking-Mechanismus bis einschlie\u00dflich <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-22-features\/\">Java 22<\/a>) zeigte das sogenannte <a href=\"https:\/\/www.happycoders.eu\/de\/java\/object-headers-compressed-class-pointers\/#mark-word\">Mark Word im Object Header<\/a> auf eine separate Lock-Datenstruktur auf dem Stack. Wenn der Stack beim Unmounten auf dem Heap geparkt und beim Mounten zur\u00fcck auf den Stack verschoben wird, k\u00f6nnte er an einer anderen Speicheradresse landen \u2013 und das w\u00fcrde diese Zeiger ung\u00fcltig machen. Ab <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-23-features\/#change-lockingmode-default-from-lm_legacy-to-lm_lightweight\">Java 23<\/a> wurde das neue \u201eLightweight Locking\u201c zum Standard-Locking-Mechanismus, das ohne Zeiger auf den Stack auskommt.<br><br><strong>Grund 2: Tracking des Plattform-Threads.<\/strong> Beim Einsatz von <code>synchronized<\/code> trackte die JVM, welcher Plattform-Thread gerade welchen Objekt-Monitor h\u00e4lt. Betrat ein virtueller Thread einen <code>synchronized<\/code>-Block, blockierte und wurde vom Carrier-Thread genommen, und wurde anschlie\u00dfend ein anderer virtueller Thread auf diesen Carrier-Thread gemounted, dann h\u00e4tte dieser andere virtuelle Thread ebenfalls den <code>synchronized<\/code>-Block betreten k\u00f6nnen. Diesen Mechanismus hat <a href=\"https:\/\/openjdk.org\/jeps\/491\" target=\"_blank\" rel=\"noopener\">JEP 491<\/a> in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-24-features\/#synchronize-virtual-threads-without-pinning-jep-491\">Java 24<\/a> ge\u00e4ndert: Seitdem kann ein virtueller Thread Monitore unabh\u00e4ngig von seinem Tr\u00e4ger-Thread halten und freigeben.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<h4 class=\"wp-block-heading\">Erkennen von Pinning<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Verbleibendes Pinning (z. B. durch nativen Code) erkennst du \u00fcber das JDK-Flight-Recorder-Event <code>jdk.VirtualThreadPinned<\/code>, etwa in einer JFR-Aufzeichnung. Das Event wird aufgezeichnet, wenn ein virtueller Thread blockiert, w\u00e4hrend er gepinnt ist.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-bb8c1fba alignfull uagb-is-root-container\">\n<div class=\"uagb-block-19a5decc uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><strong>Verhalten in fr\u00fcheren Java-Versionen<\/strong><br><br>Bis Java 23 konntest du Pinning mit der VM-Option <code>-Djdk.tracePinnedThreads=full\/short<\/code> als vollst\u00e4ndigen bzw. gek\u00fcrzten Stack-Trace ausgeben und einen <code>synchronized<\/code>-Block um eine blockierende Operation durch ein <code>ReentrantLock<\/code> ersetzen, um Pinning zu vermeiden. Beides ist seit Java 24 nicht mehr n\u00f6tig: <code>synchronized<\/code> pinnt nicht mehr, und die Option <code>-Djdk.tracePinnedThreads<\/code> wurde mit <a href=\"https:\/\/openjdk.org\/jeps\/491\" target=\"_blank\" rel=\"noopener\">JEP 491<\/a> entfernt.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"3-keine-deadlock-erkennung-in-thread-dumps\">3. Keine Deadlock-Erkennung in Thread Dumps<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Seit Java 25 enthalten die neuen Thread-Dumps (per <code>jcmd &lt;pid&gt; Thread.dump_to_file<\/code>, siehe n\u00e4chster Abschnitt) auch Informationen \u00fcber Locks, die von virtuellen Threads gehalten werden oder durch die sie blockiert werden \u2013 etwa den Objekt-Monitor, auf den ein Thread beim Eintritt wartet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Was diese Thread-Dumps weiterhin nicht anzeigen, sind <em>Deadlocks<\/em> zwischen virtuellen Threads oder zwischen einem virtuellen und einem Plattform-Thread. Eine automatische Deadlock-Erkennung wie bei klassischen Thread-Dumps gibt es hier nicht \u2013 Deadlocks musst du anhand der Lock-Informationen selbst aufsp\u00fcren.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-ed88eb83 alignfull uagb-is-root-container\">\n<div class=\"uagb-block-775dd537 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><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\"><strong>Verhalten in fr\u00fcheren Java-Versionen<\/strong><br><br>Bis Java 24 enthielten Thread-Dumps \u00fcberhaupt keine Daten \u00fcber Locks, die von virtuellen Threads gehalten werden oder durch die sie blockiert werden. Erst seit Java 25 stehen diese Lock-Informationen im Thread-Dump zur Verf\u00fcgung.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"thread-dumps-mit-virtuellen-threads\">Thread Dumps mit virtuellen Threads<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die herk\u00f6mmlichen Thread-Dumps, die per <code>jcmd &lt;pid&gt; Thread.print<\/code> ausgegeben werden, enthalten \u00fcbrigens keine virtuellen Threads. Der Grund daf\u00fcr ist, dass dieses Kommando die VM anh\u00e4lt, um einen Snapshot der laufenden Threads zu erstellen. Dies ist f\u00fcr einige hundert oder sogar einige tausend Threads machbar, aber nicht f\u00fcr Millionen von ihnen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Daher wurde eine neue Variante von Thread-Dumps implementiert, bei der die VM nicht angehalten wird (entsprechend ist der Thread-Dump ggf. nicht in sich konsistent), die daf\u00fcr aber virtuelle Threads mit einschlie\u00dft. Dieser neue Thread-Dump kann mit einem der beiden folgenden Kommandos erzeugt werden:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>jcmd &lt;pid&gt; Thread.dump_to_file -format=plain &lt;file&gt;<\/code><\/li>\n\n\n\n<li><code>jcmd &lt;pid&gt; Thread.dump_to_file -format=json &lt;file&gt;<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Das erste Kommando generiert einen Thread-Dump \u00e4hnlich des bisherigen, mit Thread-Namen, -IDs und Stack-Traces. Das zweite Kommando generiert eine Datei im JSON-Format, die dar\u00fcber hinaus Informationen \u00fcber Thread-Container, Eltern-Container und Eigent\u00fcmer-Threads enth\u00e4lt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wann-sollten-virtuelle-threads-eingesetzt-werden\">Wann sollten virtuelle Threads eingesetzt werden?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Virtuelle Threads solltest du einsetzen bei vielen nebenl\u00e4ufig abzuarbeitenden Tasks, die in erster Linie blockierende Operationen enthalten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dies gilt f\u00fcr die meisten Serveranwendungen. Wenn deine Serveranwendung allerdings CPU-intensive Aufgaben bearbeitet, solltest du daf\u00fcr Plattform-Threads verwenden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"was-gilt-es-sonst-noch-zu-beachten\">Was gilt es sonst noch zu beachten?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hier ein paar Tipps zum Einsatz von und zum Umstieg auf virtuelle Threads:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Auch wenn viele Artikel \u00fcber virtuelle Threads uns das glauben machen wollen: sie verwenden nicht grunds\u00e4tzlich weniger Speicher als ein Plattform-Thread. Dies ist nur dann der Fall, wenn der Call-Stack nicht sehr tief ist. Bei tiefen Call-Stacks verbrauchen beide Arten von Threads die gleiche Menge an Speicher.<\/li>\n\n\n\n<li>Virtuelle Threads m\u00fcssen nicht gepoolt werden. Ein Pool wird verwendet, um teure Ressourcen zu teilen. Virtuelle Threads sind hingegen so billig, dass es besser ist, einen zu erstellen, wenn man ihn braucht, und ihn terminieren zu lassen, wenn man ihn nicht mehr braucht.<\/li>\n\n\n\n<li>Wenn du den Zugriff auf eine Ressource begrenzen musst, z. B. wie viele Threads gleichzeitig auf eine Datenbank oder eine API zugreifen d\u00fcrfen, verwende statt einem Thread-Pool einen Semaphor.<\/li>\n\n\n\n<li>Ein Gro\u00dfteil des Codes f\u00fcr virtuelle Threads ist in Java geschrieben. Dementsprechend musst du die JVM aufw\u00e4rmen, bevor du Performance-Tests durchf\u00fchrst, damit der gesamte Bytecode kompiliert und optimiert ist, bevor die Messung beginnt.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"fazit\">Fazit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Virtuelle Threads halten, was sie versprechen: Sie erm\u00f6glichen es uns lesbaren und wartbaren, sequentiellen Code zu schreiben, der Betriebssystem-Threads nicht blockiert, wenn auf Locks, blockierende Datenstrukturen oder Antworten vom Dateisystem oder externen Services gewartet werden muss.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Virtuelle Threads k\u00f6nnen in der Gr\u00f6\u00dfenordnung von mehreren Millionen erzeugt werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die g\u00e4ngigen Backend-Frameworks wie Spring und Quarkus k\u00f6nnen bereits mit virtuellen Threads umgehen. Dennoch solltest du Anwendungen intensiv testen, wenn du sie auf virtuelle Threads umstellst. Beachte, dass du auf ihnen keine CPU-intensiven Rechenaufgaben ausf\u00fchrst, dass sie nicht durch das Framework gepoolt werden und dass in ihnen keine ThreadLocals gespeichert werden (s. auch <a href=\"\/de\/java\/scoped-values\/\">Scoped Values<\/a>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ich hoffe, du bist ebenso begeistert wie ich und kannst es nicht abwarten virtuelle Threads in deinen Projekten einzusetzen!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn du noch Fragen hast, stelle sie gerne \u00fcber die Kommentar-Funktion.<\/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 neuen 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>Virtuelle Threads in Java (Project Loom): Was sind virtuelle Threads? Warum brauchen wir sie? Wie funktionieren sie? Wie setzt man sie ein?<\/p>\n","protected":false},"author":1,"featured_media":34146,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_titles_title":"","_seopress_titles_desc":"Virtuelle Threads in Java (Project Loom): Wie sie funktionieren und wie du Millionen blockierende Tasks ohne async-Code bew\u00e4ltigst \u2013 mit Beispielen.","_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":"virtuelle threads,virtueller thread,virtual thread,virtual threads","_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":27550,"_post_count":0,"footnotes":""},"categories":[64],"tags":[220],"class_list":["post-31531","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\/06\/virtual-threads-java.jpg",1770,986,false],"thumbnail":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java.jpg",150,84,false],"medium":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java.jpg",300,167,false],"medium_large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java.jpg",768,428,false],"large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java.jpg",1024,570,false],"feature_thumb_224":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-224x125.jpg",224,125,true],"feature_thumb_336":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-336x187.jpg",336,187,true],"feature_thumb_504":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-504x281.jpg",504,281,true],"feature_thumb_672":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-672x374.jpg",672,374,true],"half_400":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-400x223.jpg",400,223,true],"half_600":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-600x334.jpg",600,334,true],"full_800":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-800x446.jpg",800,446,true],"full_944":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-944x526.jpg",944,526,true],"full_1200":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-1200x668.jpg",1200,668,true],"wide_1180":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-1180x490.jpg",1180,490,true],"wide_1770":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java-1770x735.jpg",1770,735,true],"1536x1536":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java.jpg",1536,856,false],"2048x2048":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2022\/06\/virtual-threads-java.jpg",1770,986,false]},"uagb_author_info":{"display_name":"Sven Woltmann","author_link":"https:\/\/www.happycoders.eu\/de\/author\/sven\/"},"uagb_comment_info":4,"uagb_excerpt":"Virtuelle Threads in Java (Project Loom): Was sind virtuelle Threads? Warum brauchen wir sie? Wie funktionieren sie? Wie setzt man sie ein?","public_identification_id":"2595b75070774c7ebaad67c91dd4b2be","private_identification_id":"bda90d176d5a4570843d6d253591146a","_links":{"self":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/31531","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=31531"}],"version-history":[{"count":35,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/31531\/revisions"}],"predecessor-version":[{"id":56838,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/31531\/revisions\/56838"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media\/34146"}],"wp:attachment":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media?parent=31531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/categories?post=31531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/tags?post=31531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}