Hi Stig, Thank you very much, and thanks to everyone who participated in this discussion. I have basically reached the following conclusions:
1. Replace libraries that use ThreadLocal for buffer pools, or refactor them to object pools wherever possible. 2. If conclusion 1 cannot be completed in the short term, pooling virtual threads appears to be the only practical option. I will do my best to implement conclusion 1; until then I will adopt conclusion 2 as an interim solution. Thanks again, Jianbin Chen Stig Rohde Døssing <[email protected]> 于2026年1月25日周日 18:58写道: > Hi Jianbin, > > 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. > > 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 > 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 > > 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?" > > 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. > > 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. > > 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. > > 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 *as* good as thread-per-task. > > 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. > > 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. > > Den søn. 25. jan. 2026 kl. 06.57 skrev Jianbin Chen <[email protected]>: > >> Hi Stig, >> >> 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. >> >> 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. >> >> Thanks, >> Jianbin Chen >> >> Stig Rohde Døssing <[email protected]> 于2026年1月25日周日 00:11写道: >> >>> Hi Jianbin, >>> >>> 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. >>> >>> 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. >>> >>> 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. >>> >>> 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. >>> >>> 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? >>> >>> Den lør. 24. jan. 2026 kl. 14.14 skrev Jianbin Chen <[email protected] >>> >: >>> >>>> Hi Alan, >>>> >>>> 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. >>>> >>>> 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. >>>> >>>> Thank you, >>>> Jianbin Chen >>>> >>>> Alan Bateman <[email protected]> 于2026年1月24日周六 16:34写道: >>>> >>>>> >>>>> >>>>> On 24/01/2026 05:55, Jianbin Chen wrote: >>>>> > : >>>>> > >>>>> > I constructed the Executor directly with >>>>> > Executors.newVirtualThreadPerTaskExecutor(); >>>>> > however, the run results still show that the pooled virtual‑thread >>>>> > behavior outperforms the non‑pooled virtual threads. >>>>> >>>>> This looks like it is benchmarking Thread.sleep so a different topic >>>>> to >>>>> that of libraries that are caching objects in thread locals. >>>>> >>>>> For the Thread.sleep test then it would easier to discuss if converted >>>>> to a JMH benchmark as there are warmup issues in the test you >>>>> included. >>>>> Also just to note that the Thread.sleep implementation has changed >>>>> significantly changed since JDK 21 so you will see very different >>>>> results with JDK 25 runs (some of the messages in the discussion speak >>>>> of JDK 21, the subject line in the mails say "JDK 25", so I'm guessing >>>>> you might be testing both). >>>>> >>>>> -Alan >>>>> >>>>
