Home > Articles

This chapter is from the book

3.6 Using Thread Executor Services

An executor service implements the ExecutorService interface that extends the Executor interface. It provides methods that facilitate:

  • Flexible submitting of tasks to the executor service and handling of results that are returned from executing tasks.

  • Managing the lifecycle of an executor service: creating, running, shutdown, and termination of the executor service.

The Executors utility class provides two methods newVirtualThreadPerTaskExecutor() and newThreadPerTaskExecutor() to obtain one-thread-per-task executor services. Both the executor services implement the AutoCloseable interface and are thus best deployed in a try-with-resources construct that ensures proper shutdown and termination of the executor service.

Using the Virtual-Thread-Per-Task Executor Service

The method newVirtualThreadPerTaskExecutor() returns an executor service that embodies the one-virtual-thread-per-task model of execution—in other words, it exclusively uses a new virtual thread to execute a task.

In Example 3.6, the code at (2) creates a one-virtual-thread-per-task executor service that will create a virtual thread for each task that is submitted. Note there is no way to set any property of a virtual thread that the executor service creates. For example, we cannot set the name of a virtual thread in this executor service.

Customizing the Thread-Per-Task Executor Service

The method newThreadPerTaskExecutor() returns an executor service that is more customizable by a thread factory for executing one thread per task in general. The kind of threads the executor service will create and deploy depends on the thread factory passed to the method.

In Example 3.6, the code at (3) creates a one-thread-per-task executor service that will create a new virtual thread for each task that is submitted, as it is passed a virtual thread factory that is also customized to use a naming scheme for the virtual threads created. This naming scheme for the virtual threads is reflected in the output.

Note that submitting a task to an executor service using the submit() method is an asynchronous operation—that is, the method returns immediately. The try-with-resources construct used to manage the executor service ensures that there is an orderly shutdown and termination of the executor service when the submitted tasks have completed execution.

The handling of the result returned by a task that is implemented as a Callable<V> object and submitted to an execution service for execution by a virtual thread is no different than if it was by a platform thread, requiring polling of the Future<V> object that receives the result.

Example 3.6 Using One-Thread-Per-Task Executor Service

package vt;
import java.util.concurrent.*;
import java.util.stream.IntStream;

public class OneThreadPerTaskExecutorDemo {
  public static final int NUM_OF_TASKS = 5;

  public static void main(String[] args) throws InterruptedException {

    // Create a task:                                                      (1)
    Runnable task = () -> System.out.printf("%s: I am on it!%n",
        Thread.currentThread());

    // Using an ExecutorService for running one virtual thread per task:   (2)
    try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
      IntStream.range(0, NUM_OF_TASKS).forEach(i -> executor.submit(task));
    }

    System.out.println(new StringBuffer().repeat('-', 69));

    // Using a customized virtual thread factory with an ExecutorService   (3)
    // for running one virtual thread per task.
    ThreadFactory vtf = Thread.ofVirtual().name("VT_", 1).factory();
    try (ExecutorService executor = Executors.newThreadPerTaskExecutor(vtf)) {
      IntStream.range(0, NUM_OF_TASKS).forEach(i -> executor.submit(task));
    }
  }
}

Probable output from the program:

VirtualThread[#22]/runnable@ForkJoinPool-1-worker-2: I am on it!
VirtualThread[#20]/runnable@ForkJoinPool-1-worker-3: I am on it!
VirtualThread[#23]/runnable@ForkJoinPool-1-worker-1: I am on it!
VirtualThread[#24]/runnable@ForkJoinPool-1-worker-2: I am on it!
VirtualThread[#25]/runnable@ForkJoinPool-1-worker-1: I am on it!
---------------------------------------------------------------------
VirtualThread[#30,VT_1]/runnable@ForkJoinPool-1-worker-1: I am on it!
VirtualThread[#31,VT_2]/runnable@ForkJoinPool-1-worker-5: I am on it!
VirtualThread[#32,VT_3]/runnable@ForkJoinPool-1-worker-2: I am on it!
VirtualThread[#33,VT_4]/runnable@ForkJoinPool-1-worker-5: I am on it!
VirtualThread[#34,VT_5]/runnable@ForkJoinPool-1-worker-2: I am on it!

The Executors Utility Class

The Executors utility class provides the following methods to create executor services that implement the one-thread-per-task model of execution:

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.