<div dir="ltr"><div>Hi Jianbin,</div><div><br></div><div>It might be worth considering that depending on unmaintained or poorly maintained libraries is a risk for your application (what happens when a vulnerability is discovered?), even before virtual threads enter the equation.</div><div><br></div><div>Creating an object pool should not require any post-Java-8 features, so
it should be possible to update libraries to be virtual thread friendly
without dropping compatibility with Java 8. With a bit of abstraction, the library could even allow sticking with TLs depending on configuration, see for example log4j's Recycler interface <a href="https://github.com/apache/logging-log4j2/blob/4f474b32751f4ccad67424ca585612584440cd63/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java">https://github.com/apache/logging-log4j2/blob/4f474b32751f4ccad67424ca585612584440cd63/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/Recycler.java</a></div><div><br></div><div>However, the question you are asking is essentially "Assuming I can't change the libraries, is it okay to pool virtual threads as a workaround?"</div><div><br></div><div>The reasoning behind never pooling virtual threads, given in JEP 444, is that virtual threads are "cheap and plentiful", allowing for the use of the thread-per-task style rather than pooling and reusing threads. With the libraries you are using, threads cannot be cheap and plentiful, because the TLs make them expensive. </div><div><br></div><div>Assuming the libraries can't be changed, you can't use the thread-per-task style, so you are forced into pooling. Once you are stuck with pooling, you are asking if it's fine for the pool to create virtual threads instead of platform threads, because virtual threads are cheaper to create when the pool needs to scale.</div><div><br></div><div>And it probably is, and there probably aren't better alternatives if we stick with your premise that the libraries can't be changed. Pooling virtual threads isn't "against the rules", it's just not recommended, because ideally you'd change the library rather than hacking around the problem by pooling threads. </div><div><br></div><div>The drawback to this workaround is that you have to abandon the thread-per-task style, which is where a lot of the benefit of virtual threads comes from. You end up with something that behaves mostly like a normal platform thread pool, except the pool can have more threads than usual, and creating those threads is faster than usual. That's good, it's just not <i>as</i> good as thread-per-task. </div><div><br></div><div>As a side note, you might want to consider limiting the pool size, or limiting your concurrency in other ways, e.g. via semaphores. Since the TL resources are expensive, it doesn't seem like a good idea to have no limit on how many threads you can have active at a time.</div><div><br></div><div>My 2 cents on your question are that what you are doing is probably fine as a short term workaround, but you should really consider putting the necessary time into updating those libraries as a longer term solution. You will end up with a better result.</div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">Den søn. 25. jan. 2026 kl. 06.57 skrev Jianbin Chen <<a href="mailto:jianbin@apache.org">jianbin@apache.org</a>>:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">Hi Stig,<br><br>I mostly agree with your view. My emails have been describing a specific scenario: my application runs on JDK 25, but many of the libraries I depend on were developed for JDK 8 or are not very actively maintained. In the short term, pooling virtual threads seems to be the only practical workaround; I don’t see a better alternative right now.<br><br>One correction I need to make: I did not fix the maximum size of my virtual‑thread pool. That means when the 200 core virtual threads are all in use, the pool’s behavior becomes the same as non‑pooled virtual threads (it will create additional threads). You suggested using platform threads instead, but platform threads have expensive context switching. In my example, if I switch to platform threads then once the 200 core threads are exhausted new platform threads are created, and at the moment those threads are created CPU usage essentially spikes. If you run Java under Kubernetes you’ll be familiar with this: creating new platform threads can instantly consume the cgroup CPU quota, causing the process to be throttled until the next available CPU window. Using a pooled virtual‑thread solution avoids this problem because it does not require creating costly platform threads.<br><br>Thanks, <br>Jianbin Chen</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Stig Rohde Døssing <<a href="mailto:stigdoessing@gmail.com" target="_blank">stigdoessing@gmail.com</a>> 于2026年1月25日周日 00:11写道:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>Hi Jianbin,</div><div><br></div><div>Sorry to butt in, but I think the question you are asking is a little odd. You have a library that uses ThreadLocals for reusing expensive resources (buffers in this case). The way to make such a library work well with virtual threads is to redesign the library to avoid using TLs in this manner. For example, you could make the library keep a pool of these resources for reuse in a non-TL structure, like concurrent maps/lists/queues. </div><div><br></div><div>But once you set the limitation that the library can't be adjusted, you are forced into awkward workarounds. This is because the main advantage of virtual threads is to allow you to write code in a thread-per-task style, but the presence of these TLs makes threads precious resources that must be reused across tasks, which loses you the ability to use virtual threads in this way.</div><div><br></div><div>
If you are unable to adjust the library and really want to use virtual
threads for part of your code, an option is to isolate the TL-using
code so it runs on a platform thread pool. You would then write most of
your code in thread-per-task style with virtual threads, but make the
virtual threads hand off work that needs the TLs to the thread pool,
blocking the virtual thread until that work completes.
<br></div><div><br></div><div>If that is not an option, and you don't want that kind of handoff, you are forced to create a pool of threads, as you found. But at that point, I don't really understand why you want to use virtual threads at all. Once you are making a pool of 200 threads you reuse, it doesn't really matter if those threads are virtual or platform threads. You are forced to abandon the thread-per-task style either way.</div><div><br></div><div>I don't think there is a great solution that will let you use thread-per-task style virtual threads with a library that uses TLs for resource reuse. The best you are likely to be able to do is various workarounds, with various drawbacks. It might be better to aim for reworking the library, and sticking with platform threads until you can do that?</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Den lør. 24. jan. 2026 kl. 14.14 skrev Jianbin Chen <<a href="mailto:jianbin@apache.org" target="_blank">jianbin@apache.org</a>>:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">Hi Alan,<br><br>I ran my example on JDK 21 because it uses Thread.sleep. In an earlier message on the mailing list I learned that virtual‑thread performance on JDK 25 was worse for this kind of scenario compared with JDK 21, and that the issue is supposed to be fixed in JDK 25.0.3 — which has not been released yet. <br><br>That said, this does not affect the main point of my message: I’m asking for advice about using pooled virtual threads to work around third‑party libraries that implement buffer pools via ThreadLocal.<br><br>Thank you, <br>Jianbin Chen</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Alan Bateman <<a href="mailto:alan.bateman@oracle.com" target="_blank">alan.bateman@oracle.com</a>> 于2026年1月24日周六 16:34写道:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><br>
<br>
On 24/01/2026 05:55, Jianbin Chen wrote:<br>
> :<br>
><br>
> I constructed the Executor directly with <br>
> Executors.newVirtualThreadPerTaskExecutor();<br>
> however, the run results still show that the pooled virtual‑thread <br>
> behavior outperforms the non‑pooled virtual threads.<br>
<br>
This looks like it is benchmarking Thread.sleep so a different topic to <br>
that of libraries that are caching objects in thread locals.<br>
<br>
For the Thread.sleep test then it would easier to discuss if converted <br>
to a JMH benchmark as there are warmup issues in the test you included. <br>
Also just to note that the Thread.sleep implementation has changed <br>
significantly changed since JDK 21 so you will see very different <br>
results with JDK 25 runs (some of the messages in the discussion speak <br>
of JDK 21, the subject line in the mails say "JDK 25", so I'm guessing <br>
you might be testing both).<br>
<br>
-Alan<br>
</blockquote></div>
</blockquote></div>
</blockquote></div>
</blockquote></div>