According to a request of the OP to configure VirtualThread with a custom thread pool, the following solution is discussed below.
As of JDK 21 there is no way to set up a custom Executor/scheduler for VirtualThread class: this class along with ThreadBuilders.VirtualThreadBuilder and VirtualThreadFactory are java.lang package-private and the only way to do so is to use Reflection.
public static Thread.Builder customVirtualThreadPool(Executor executor) throws ReflectiveOperationException { var ctor = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder") .getDeclaredConstructor(Executor.class); ctor.setAccessible(true); return (Thread.Builder) ctor.newInstance(executor); }
As access to java.lang classes is attempted it might be necessary to explicitly allow it in application parameters
--add-opens=java.base/java.lang=ALL-UNNAMED
as Module java.base does not "opens java.lang" thread suggests.
Note that this can be used to set up custom ForkJoinPool instance as an actual pool in order to get an access to work-stealing count.
For Spring Boot/Tomcat application further configuration does not differ from configuration of any other such Executor.
As usual, to run Tomcat worker threads on this Excecutor, the ones that executes @Controllers methods, annotated with @RequestMapping and analogous, customize Tomcat Protocol Handler:
@Bean public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() { return protocolHandler -> { try { protocolHandler.setExecutor( customVirtualThreadPool(Executors.newCachedThreadPool())::start); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create custom Virtual Thread Executor", e); } }; }
Similarly, to run Spring-controlled asynchronous methods on this Executor, configuration will look like this:
@Configuration @EnableAsync public class AppConfig { @Bean public TaskExecutor taskExecutor() { try { return new TaskExecutorAdapter(customVirtualThreadPool(Executors.newCachedThreadPool())::start); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create custom Virtual Thread Executor", e); } } }
and the asynchronous method like this:
@Service @Async // will use `taskExecutor` by default, if there are more than one in the app, then Bean name can be specified public class AsyncService { public void doSomething() { ... } }
Note that presence of spring.threads.virtual.enabled = true in application property file is not required in both cases.
The above is applicable to Spring Boot 3.2.0; be cautious with older Tomcats, they may or may not support the configuration above.
ForkJoinPoolwas selected.ForkJoinPoolis doing (and what would be a benefit of it?).ForkJoinPoolis just most advanced concurrentExecutorServiceJDK has. In comparison to older 1.5ThreadPoolExecutor, which uses older AQS-based technique, FJP uses "jdk-internalUnsafefor atomics and special memory modes", which presumably more efficient "in very racy code".Executor. Every virtual thread is “stolen”…