Skip to main content
Updated according to comments
Source Link
igor.zh
  • 2.8k
  • 23
  • 26

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. The solution is wrapped below in an implementation of Executor interface:

public class CustomVirtualThreadPoolExecutor implements Executor { private final Object builderInstance; private final Class<?> builderClass; private UncaughtExceptionHandler uhe; public CustomVirtualThreadPoolExecutor() throws ReflectiveOperationException {  static this(ExecutorsThread.newCachedThreadPool()); } publicBuilder CustomVirtualThreadPoolExecutorcustomVirtualThreadPool(Executor executor) throws ReflectiveOperationException {  try {   var builderClassctor = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");   final Constructor<?> ctor = builderClass.getDeclaredConstructor(Executor.class);   ctor.setAccessible(true); builderInstance = ctor.newInstance(executor); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new ReflectiveOperationException(e); } } public void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { this.uhe = Objects.requireNonNull(ueh); } @Override public void execute(final Runnable task) { try { final Method startMethod = builderClass.getMethod("start", Runnable.class);   return startMethod.setAccessible(true); startMethodThread.invoke(builderInstance, task); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) { if (uhe != nullBuilder)  uhector.uncaughtExceptionnewInstance(null, eexecutor);  } } } 

As access to java.lang classes is attempted it might be necessary to explicitly allow it in application parameters

@Configuration public class AppConfig { @Bean  public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {   return protocolHandler -> { try {  try { protocolHandler.setExecutor( protocolHandler.setExecutor(new CustomVirtualThreadPoolExecutor customVirtualThreadPool(Executors.newCachedThreadPool())::start);   } catch (ReflectiveOperationException e) {   throw new BeanInitializationException("Cannot create "custom +Virtual CustomVirtualThreadPoolExecutor.class.getSimpleName()Thread Executor", e); } } }  ; } 
@Configuration @EnableAsync public class AppConfig { @Bean public TaskExecutor taskExecutor() { try { return new TaskExecutorAdapter(new CustomVirtualThreadPoolExecutorcustomVirtualThreadPool(Executors.newCachedThreadPool())::start); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create "custom +Virtual CustomVirtualThreadPoolExecutor.class.getSimpleName()Thread Executor", e); } } } 

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. The solution is wrapped below in an implementation of Executor interface:

public class CustomVirtualThreadPoolExecutor implements Executor { private final Object builderInstance; private final Class<?> builderClass; private UncaughtExceptionHandler uhe; public CustomVirtualThreadPoolExecutor() throws ReflectiveOperationException {  this(Executors.newCachedThreadPool()); } public CustomVirtualThreadPoolExecutor(Executor executor) throws ReflectiveOperationException {  try {   builderClass = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");   final Constructor<?> ctor = builderClass.getDeclaredConstructor(Executor.class);   ctor.setAccessible(true); builderInstance = ctor.newInstance(executor); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new ReflectiveOperationException(e); } } public void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { this.uhe = Objects.requireNonNull(ueh); } @Override public void execute(final Runnable task) { try { final Method startMethod = builderClass.getMethod("start", Runnable.class);   startMethod.setAccessible(true); startMethod.invoke(builderInstance, task); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) { if (uhe != null)  uhe.uncaughtException(null, e);  } } } 

As access to java.lang classes is attempted it might be necessary to explicitly allow it in parameters

@Configuration public class AppConfig { @Bean  public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {   return protocolHandler -> { try { protocolHandler.setExecutor(new CustomVirtualThreadPoolExecutor());   } catch (ReflectiveOperationException e) {   throw new BeanInitializationException("Cannot create " + CustomVirtualThreadPoolExecutor.class.getSimpleName(), e); } } }   } 
@Configuration @EnableAsync public class AppConfig { @Bean public TaskExecutor taskExecutor() { try { return new TaskExecutorAdapter(new CustomVirtualThreadPoolExecutor()); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create " + CustomVirtualThreadPoolExecutor.class.getSimpleName(), e); } } } 

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

@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); } }; } 
@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); } } } 
Fixed code
Source Link
igor.zh
  • 2.8k
  • 23
  • 26

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. The solution is wrapped below in an implementation of Executor interface:

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:

@Configuration public class AppConfig { @Bean public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() { return protocolHandler -> { try { protocolHandler.setExecutor(new CustomVirtualThreadPoolExecutor()); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create " + CustomVirtualThreadPoolExecutor.class.getSimpleName(), 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(new CustomVirtualThreadPoolExecutor()); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create " + CustomVirtualThreadPoolExecutor.class.getSimpleName(), 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.

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. The solution is wrapped below in an implementation of Executor interface:

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.

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. The solution is wrapped below in an implementation of Executor interface:

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:

@Configuration public class AppConfig { @Bean public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() { return protocolHandler -> { try { protocolHandler.setExecutor(new CustomVirtualThreadPoolExecutor()); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create " + CustomVirtualThreadPoolExecutor.class.getSimpleName(), 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(new CustomVirtualThreadPoolExecutor()); } catch (ReflectiveOperationException e) { throw new BeanInitializationException("Cannot create " + CustomVirtualThreadPoolExecutor.class.getSimpleName(), 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.

Source Link
igor.zh
  • 2.8k
  • 23
  • 26

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. The solution is wrapped below in an implementation of Executor interface:

public class CustomVirtualThreadPoolExecutor implements Executor { private final Object builderInstance; private final Class<?> builderClass; private UncaughtExceptionHandler uhe; public CustomVirtualThreadPoolExecutor() throws ReflectiveOperationException { this(Executors.newCachedThreadPool()); } public CustomVirtualThreadPoolExecutor(Executor executor) throws ReflectiveOperationException { try { builderClass = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder"); final Constructor<?> ctor = builderClass.getDeclaredConstructor(Executor.class); ctor.setAccessible(true); builderInstance = ctor.newInstance(executor); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new ReflectiveOperationException(e); } } public void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { this.uhe = Objects.requireNonNull(ueh); } @Override public void execute(final Runnable task) { try { final Method startMethod = builderClass.getMethod("start", Runnable.class); startMethod.setAccessible(true); startMethod.invoke(builderInstance, task); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) { if (uhe != null) uhe.uncaughtException(null, e); } } } 

As access to java.lang classes is attempted it might be necessary to explicitly allow it in 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.