{"id":31531,"date":"2022-06-13T19:30:00","date_gmt":"2022-06-13T17:30:00","guid":{"rendered":"https:\/\/www.happycoders.eu\/?p=31531"},"modified":"2025-04-09T12:21:41","modified_gmt":"2025-04-09T10:21:41","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>Virtuelle Threads (\u201evirtual threads\u201c) sind eine der wichtigsten Neuerungen in Java seit langem. Sie wurden in <a rel=\"noopener\" href=\"https:\/\/openjdk.org\/projects\/loom\/\" target=\"_blank\">Project Loom<\/a> entwickelt und sind seit <a href=\"\/de\/java\/java-19-features\/\">Java 19<\/a> als Preview-Feature im JDK enthalten und seit <a href=\"\/de\/java\/java-21-features\/\">Java 21<\/a> in ihrer finalen Version (<a rel=\"noopener\" href=\"https:\/\/openjdk.org\/jeps\/444\" target=\"_blank\">JEP 444<\/a>).<\/p>\n\n\n\n<p>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 <em>nicht<\/em>, und welche Einschr\u00e4nkungen weisen sie auf?<\/li>\n<\/ul>\n\n\n\n<p>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>Wer jemals eine Backend-Anwendung unter hoher Last maintained 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 resourcenhungrig:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ein Betriebssystem-Thread reserviert 1 MB f\u00fcr den Stack im Vorraus 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>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>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>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>Bislang konnten wir dieses Problem nur mit asynchroner Programmierung bew\u00e4ltigen \u2013 z. B. mit <a rel=\"noopener\" href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/api\/java.base\/java\/util\/concurrent\/CompletableFuture.html\" target=\"_blank\">CompletableFuture<\/a> oder reaktiven Frameworks wie <a rel=\"noopener\" href=\"https:\/\/github.com\/ReactiveX\/RxJava\" target=\"_blank\">RxJava<\/a> und <a rel=\"noopener\" href=\"https:\/\/projectreactor.io\/\" target=\"_blank\">Project Reactor<\/a>.<\/p>\n\n\n\n<p>Wer allerdings schon einmal Code wie den folgenden maintainen 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>Nicht nur, dass dieser Code kaum lesbar ist, er ist auch extrem schwer zu debuggen. Beispielweise 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>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>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>Stattdessen gibt es einen Pool sogenannter Tr\u00e4ger-Threads (Carrier Threads), auf die ein virtueller Thread vor\u00fcbergehend gemappt wird (englisch: \u201emounted\u201d). Sobald der virtuelle Thread auf eine blockierende Operation st\u00f6\u00dft, wird der virtuelle Thread vom Tr\u00e4ger-Thread genommen (english: \u201eunmounted\u201d), und der Tr\u00e4ger-Thread kann einen anderen virtuellen Thread (einen neuen oder einen zuvor blockierten) ausf\u00fchren.<\/p>\n\n\n\n<p>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>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>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>Blockierende Operationen blockieren somit den ausf\u00fchrenden Tr\u00e4ger-Thread nicht, und wir k\u00f6nnen wir mit einem kleinen Pool von Tr\u00e4ger-Threads eine Vielzahl von Requests parallel bearbeiten.<\/p>\n\n\n\n<p>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>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>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>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>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(\n        <span class=\"hljs-string\">\"Thread %s - Task %d waiting...%n\"<\/span>, 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(\n          <span class=\"hljs-string\">\"Thread %s - Task %d canceled.%n\"<\/span>, Thread.currentThread().getName(), number);\n      <span class=\"hljs-keyword\">return<\/span> -<span class=\"hljs-number\">1<\/span>;\n    }\n\n    System.out.printf(\n        <span class=\"hljs-string\">\"Thread %s - Task %d finished.%n\"<\/span>, Thread.currentThread().getName(), number);\n    <span class=\"hljs-keyword\">return<\/span> ThreadLocalRandom.current().nextInt(<span class=\"hljs-number\">100<\/span>);\n  }\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>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-info-box uagb-block-76088612 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\"><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\n\n\n<p>Das Programm l\u00e4uft etwas \u00fcber 10 Sekunden. Das war zu erwarten:<\/p>\n\n\n\n<p class=\"has-text-align-center\">1.000 Tasks geteilt durch 100 Threads = 10 Tasks pro Thread<\/p>\n\n\n\n<p>Jeder Plattform-Thread musste 10 Tasks, die jeweils etwa 1 Sekunde dauerten, sequentiell abarbeiten.<\/p>\n\n\n\n<p>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>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>Dieser Executor verwendet keinen Thread-Pool, sondern legt f\u00fcr jeden Task einen neuen virtuellen Thread an.<\/p>\n\n\n\n<p>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>Beeindruckend: selbst 10.000 Tasks kann unser kleines Programm in etwas \u00fcber einer Sekunde abarbeiten.<\/p>\n\n\n\n<p>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>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>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});\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>Bei der zweiten Variante liefert <code>Thread.ofVirtual()<\/code> einen <code>VirtualThreadBuilder<\/code> zur\u00fcck, dessen <code>start()<\/code>-Methode einen virtuellen Thread startet. Die alternative Methode <code>Thread.ofPlatform()<\/code> liefert einen <code>PlatformThreadBuilder<\/code> zur\u00fcck, \u00fcber den ein Plattform-Thread gestartet werden kann.<\/p>\n\n\n\n<p>Beide Builder implementieren das Interface <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});\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>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>In diesem <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads\" target=\"_blank\">GitHub-Repository<\/a> findest du zahlreiche Demo-Programme, die die F\u00e4higkeiten von virtuellen Threads demonstrieren.<\/p>\n\n\n\n<p>Mit der Klasse <a rel=\"noopener\" 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\">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>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\u201d, sobald ein virtueller Thread blockiert. Kurz darauf beendete sich die Anwendung mit einem <code>OutOfMemoryError<\/code>.<\/p>\n\n\n\n<p>Mit der Klasse <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads\/blob\/main\/src\/main\/java\/eu\/happycoders\/virtualthreads\/presentation\/HowManyPlatformThreadsDoingSomething.java\" target=\"_blank\">HowManyPlatformThreadsDoingSomething<\/a> kannst du au\u00dferdem testen, wie viele <em>Plattform<\/em>-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>Die Beispiel-Methode vom Beginn dieses Artikels w\u00fcrde als Jakarta RESTful Webservices Controller \u2013 zun\u00e4chst ohne virtuelle Threads \u2013 wie folgt aussehen:<\/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\">@GET<\/span>\n<span class=\"hljs-meta\">@Path<\/span>(<span class=\"hljs-string\">\"\/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\">(@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-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>Um diesen Controller nun auf einem virtuellen Thread laufen zu lassen, m\u00fcssen wir lediglich eine einzige Zeile \u2013 die Annotation <code>@RunOnVirtualThread<\/code> \u2013 hinzuf\u00fcgen:<\/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>Am Methodenk\u00f6rper mussten wir nicht ein einziges Zeichen \u00e4ndern.<\/p>\n\n\n\n<p><code>@RunOnVirtualThread<\/code> wird in <a rel=\"noopener\" href=\"https:\/\/jakarta.ee\/specifications\/platform\/11\/\" target=\"_blank\">Jakarta EE 11<\/a> definiert, das im ersten Quartal 2024 ver\u00f6ffentlicht werden soll.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wie-verwendet-man-virtuelle-threads-mit-quarkus\">Wie verwendet man virtuelle Threads mit Quarkus?<\/h3>\n\n\n\n<p><a rel=\"noopener\" href=\"https:\/\/quarkus.io\/\" data-type=\"link\" data-id=\"https:\/\/quarkus.io\/\" target=\"_blank\">Quarkus<\/a> unterst\u00fctzt die in Jakarta EE 11 definierte <code>@RunOnVirtualThread<\/code>-Annotation bereits seit der <a rel=\"noopener\" href=\"https:\/\/quarkus.io\/blog\/quarkus-2-10-0-final-released\/\" target=\"_blank\">Version 2.10<\/a> \u2013 also seit Juni 2022. Mit einer aktuellen Quarkus-Version kannst du also den oben gezeigten Code bereits einsetzen.<\/p>\n\n\n\n<p>In diesem <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads-quarkus\" target=\"_blank\">GitHub-Repository<\/a> findest du eine Beispiel-Quarkus-Anwendung mit dem oben gezeigten Controller \u2013 einmal mit Platform-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>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 =\n      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>Zur Umstellung auf virtuelle Threads muss man allerdings etwas anders vorgehen. Laut der <a rel=\"noopener\" href=\"https:\/\/spring.io\/blog\/2022\/10\/11\/embracing-virtual-threads\/\" target=\"_blank\">Spring-Dokumentation<\/a> m\u00fcssen folgende zwei Beans definiert werden:<\/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-meta\">@Bean<\/span>(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> AsyncTaskExecutor <span class=\"hljs-title\">asyncTaskExecutor<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());\n}\n\n<span class=\"hljs-meta\">@Bean<\/span>\n<span class=\"hljs-keyword\">public<\/span> TomcatProtocolHandlerCustomizer&lt;?&gt; protocolHandlerVirtualThreadExecutorCustomizer() {\n  <span class=\"hljs-keyword\">return<\/span> protocolHandler -&gt; {\n    protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());\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>Dies f\u00fchrt jedoch dazu, dass <em>alle<\/em> Controller auf virtuellen Threads laufen, was f\u00fcr die meisten Anwendungsf\u00e4lle in Ordnung sein mag, nicht jedoch bei CPU-lastigen Aufgaben \u2013 diese sollten immer auf Platform-Threads laufen.<\/p>\n\n\n\n<p>In diesem <a rel=\"noopener\" href=\"https:\/\/github.com\/SvenWoltmann\/virtual-threads-spring\" target=\"_blank\">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<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"vorteile-von-virtuellen-threads\">Vorteile von Virtuellen Threads<\/h2>\n\n\n\n<p>Virtuelle Threads bieten beeindruckende Vorteile:<\/p>\n\n\n\n<p>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 committed, 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>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>Virtuelle Threads haben nat\u00fcrlich nicht nur Vorteile. Schauen wir uns zun\u00e4chst einmal an, was virtuelle Threads <em>nicht<\/em> sind, bzw. was wir mit ihnen <em>nicht<\/em> 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 Carriers-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>\u00dcber folgende Einschr\u00e4nkungen solltest du Bescheid wissen. Einige davon werden in zuk\u00fcnftigen Java-Versionen aufgehoben werden sein:<\/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>Obwohl die \u00fcberwiegende Mehrheit der blockierenden Operationen im JDK umgeschrieben wurde, um virtuelle Threads zu unterst\u00fctzen, gibt es immer noch Operationen, die einen virtuellen Thread nicht vom Carrier-Thread nehmen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>File I\/O \u2013 dies wird in naher Zukunft ebenfalls angepasst werden<\/li>\n\n\n\n<li><code>Object.wait()<\/code><\/li>\n<\/ul>\n\n\n\n<p>In diesen beiden F\u00e4llen wird ein blockierter virtueller Thread auch den Tr\u00e4ger-Thread blockieren. Um dies zu kompensieren, erh\u00f6hen beide Operationen vor\u00fcbergehend die Anzahl der Tr\u00e4ger-Threads \u2013 bis zu einem Maximum von 256 Threads, das \u00fcber die VM-Option <code>jdk.virtualThreadScheduler.maxPoolSize<\/code> angepasst werden kann.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"2-pinning\">2. Pinning<\/h3>\n\n\n\n<p>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\u201d wurde \u2013 was bedeutet, dass er den Tr\u00e4ger-Thread nicht wechseln darf. Dies passiert in zwei F\u00e4llen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>innerhalb eines <code>synchronized<\/code>-Blocks (gel\u00f6st in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-24-features\/#Synchronize_Virtual_Threads_without_Pinning_JEP_491\">Java 24<\/a>)<\/li>\n\n\n\n<li>wenn der Call-Stack Aufrufe nativen Codes enth\u00e4lt<\/li>\n<\/ul>\n\n\n\n<p>F\u00fcr das Pinning gibt es zwei Gr\u00fcnde:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Grund 1: Zeiger auf Speicheradressen auf dem Stack<\/h4>\n\n\n\n<p>In beiden F\u00e4llen k\u00f6nnen Zeiger auf Speicheradressen auf dem Stack existieren.<\/p>\n\n\n\n<p>Im Fall eines <code>synchronized<\/code>-Blocks und beim Einsatz des sogenannten \u201eLegacy Stack Lockings\u201c (dem Standard-Locking-Mechanismus bis einsschlie\u00dflich <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-22-features\/\">Java 22<\/a>) zeigt 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.<\/p>\n\n\n\n<p>Und was innerhalb von nativem Code passiert, k\u00f6nnen wir nicht kontrollieren.<\/p>\n\n\n\n<p>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. Und das w\u00fcrde diese Zeiger ung\u00fcltig machen.<\/p>\n\n\n\n<p>Ab <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-23-features\/\">Java 23<\/a> wurde das neue \u201eLightweight Locking\u201c zum Standard-Locking-Mechanismus. Dieses kommt ohne Pointer auf den Stack aus.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Grund 2: Tracking des Plattform-Threads<\/h4>\n\n\n\n<p>Dar\u00fcber hinaus trackt die JVM beim Einsatz von <code>synchronized<\/code>, welcher Plattform-Thread gerade welchen Objekt-Monitor h\u00e4lt. Wenn nun ein virtueller Thread einen <code>synchronized<\/code>-Block betritt, dann blockiert und vom Carrier-Thread genommen wird, dann ein anderer virtueller Thread auf diesen Carrier-Thread gemounted wird, dann k\u00f6nnte dieser andere virtuelle Thread ebenfalls den <code>synchronized<\/code>-Block betreten.<\/p>\n\n\n\n<p>Dieser Mechanimus wird in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-24-features\/#Synchronize_Virtual_Threads_without_Pinning_JEP_491\">Java 24<\/a> ge\u00e4ndert werden.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Erkennen und Verhindern von Pinning<\/h4>\n\n\n\n<p>Mit der VM-Option <code>-Djdk.tracePinnedThread=full\/short<\/code> kannst du dir einen vollst\u00e4ndigen\/gek\u00fcrzten Stack-Trace ausgeben, wenn ein virtueller Thread blockiert, w\u00e4hrend er gepinnt ist.<\/p>\n\n\n\n<p>Einen <code>synchronized<\/code>-Block um blockierende Operation kannst du durch ein <code>ReentrantLock<\/code> ersetzen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"3-keine-locks-in-thread-dumps\">3. Keine Locks in Thread Dumps<\/h3>\n\n\n\n<p>Thread-Dumps enthalten derzeit keine Daten \u00fcber Locks, die von virtuellen Threads gehalten werden oder durch die sie blockiert werden. Dementsprechend zeigen sie auch keine Deadlocks zwischen virtuellen Threads oder zwischen einem virtuellen und einem Plattform-Thread an.<\/p>\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>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>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>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>Virtuelle Threads solltest du einsetzen bei vielen nebenl\u00e4ufig abzuarbeitenden Tasks, die in erster Linie blockierende Operationen enthalten. <\/p>\n\n\n\n<p>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>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>Virtuelle Threads sind neu, und wir haben noch nicht viel Erfahrung mit ihnen, verglichen mit asynchronen oder reaktiven Frameworks. Du solltest Anwendungen mit virtuellen Threads also intensiv testen, bevor du sie in Produktion einsetzt.<\/li>\n\n\n\n<li>Auch wenn viele Artikel \u00fcber virtuelle Threads uns das glauben machen wollen: sie verwenden nicht <em>grunds\u00e4tzlich<\/em> 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. Auch hier gilt also: intensiv testen!<\/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>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>Virtuelle Threads k\u00f6nnen in der Gr\u00f6\u00dfenordnung von mehreren Millionen erzeugt werden.<\/p>\n\n\n\n<p>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 Value<\/a>). <\/p>\n\n\n\n<p>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>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 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>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_robots_primary_cat":"none","_seopress_titles_title":"","_seopress_titles_desc":"Virtuelle Threads in Java (Project Loom): Was sind virtuelle Threads? Warum brauchen wir sie? Wie funktionieren sie? Wie setzt man sie ein?","_seopress_robots_index":"","_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":25358,"_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":10,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/31531\/revisions"}],"predecessor-version":[{"id":42998,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/31531\/revisions\/42998"}],"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}]}}