Search code examples
javamultithreadingjava-21virtual-threads

Is it ok to execute a PlatformThread with a virtual-thread-per-task ExecutorService?


I understand that instead of pooling VirtualThreads, which does not really bring benefits, a Executors.newVirtualThreadPerTaskExecutor() should be used in order to wrap all of our tasks with new virtual threads.

I was wondering though if it should be discouraged to execute a PlatformThread with such an executor, and if generally Threads altogether should never be passed to an executor.

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
  Runnable platformThread = Thread.ofPlatform().unstarted(someTask);
  executor.execute(platformThread);
}

The platform thread created this way will be wrapped with a virtual thread because of the ThreadFactory of the executor. I am trying to understand if this could ever make sense, or if in general Thread objects should never be passed to ExecutorServices.


Solution

  • wrap all of our tasks with new virtual threads.

    No, not all tasks should be performed with virtual threads. Virtual threads are appropriate for tasks that:

    • Involve blocking, such as file I/O, logging, network calls, interacting with a database, and so on. In contrast, if a task is CPU-bound, such as video-encoding, use a platform thread.
    • Use synchronized only briefly, around small amounts of code that execute quckly. Long chores inside synchronized should either be refactored to use a ReentrantLock or else run in a platform thread. (This synchronized limitation may change in future versions of Java, beyond Java 21.)
    • Execute within the JVM. If your task involves calling native code such as Java Native Interface (JNI), use platform threads.

    Runnable platformThread = Thread.ofPlatform().unstarted(someTask);

    As others commented, your code does not actually run in a platform thread. Your use of Thread as a Runnable means the run method of your task will execute on a new virtual thread in the existing executor service. The Thread#start method of your object will never be called, and so no platform thread will execute as a result of this code.

    You’ve fallen into the trap of using Thread as a Runnable. Having made the Thread class implement the Runnable interface is a flaw in the API, a regrettable design choice.

    in general Thread objects should never be passed to ExecutorServices

    As stated above, effectively you passed a Runnable to your executor service, not a Thread. Your code never started your Thread object, never used any Thread feature.

    generally Threads altogether should never be passed to an executor

    The entire purpose of the Executors framework is to relieve us Java programmers of the burden of managing threads. In modern Java we rarely deal with Thread directly.

    Focus on defining your tasks as Runnable/Callable objects, and submitting them to an executor service. The only time we need to think about threads is in choosing what kind of executor service to establish: virtual versus platform threads, and if platform threads, how many.

    Keep in mind that you can maintain more than one executor service at runtime:

    • Keep one executor service backed by platform threads for your CPU-bound tasks like video-encoding.
    • Keep another executor service backed by virtual threads for most other tasks.