Why does the ExecutorService interface not implement AutoCloseable?

10.4k views Asked by At

Failing to call shutdown() on a thread executor will result in a never terminating application.

Best practice to shut down the ExecutorService is this:

ExecutorService service = null;
try {
  service = Executors.newSingleThreadExecutor();
  // add tasks to thread executor
  …
} finally {
  if (service != null) service.shutdown();
}

Since Java knows the try-with-resources concept, wouldn't it be nice if we could do this?

try (service = Executors.newSingleThreadExecutor())
{
  // add tasks to thread executor
  …
} 
4

There are 4 answers

4
GhostCat On BEST ANSWER

That ExecutorService has actually two shutdown-related methods; based on the simple fact that both ways of shutting down a service make sense.

Thus: how would you auto-close a service then? In a consistent manner that works for everybody?!

So, the reasonable explanation in my eyes: you can't make an ExecutorService a AutoClosable because that service does not have a single "close" like operation; but two!

And if you think you could make good use of such an auto-closing service, writing up your own implementation using "delegation" would be a 5 minute thing! Or probably 10 minutes, because you would create one version calling shutdown() as close operation; and one that does shutdownNow() instead.

2
Lukas Eder On

This is a mediocre workaround

ExecutorService service = Executors.newSingleThreadExecutor();
try (Closeable close = service::shutdown) {

}

Or, if the checked exception bothers you, you could write:

interface MyCloseable extends AutoCloseable {
    void close();
}

And then

ExecutorService service = Executors.newSingleThreadExecutor();
try (MyCloseable close = service::shutdown) {

}

Of course, you must never ever put anything between the assignment and the try statement, nor use the service local variable after the try statement.

Given the caveats, just use finally instead.

1
Nathan Hughes On

I don't see where AutoCloseable is that useful for an Executor. try-with-resources is for things that can be initialized, used, and released within the scope of a method. This works great for things like files, network connections, jdbc resources, etc., where they get opened, used, and cleaned up quickly. But an executor, especially a threadpool, is something you want available for a long time period, probably over the lifetime of the application, and will tend to get injected into things like singleton services, which can have a method that the DI framework knows to call on application shutdown to clean up the executor. This usage pattern works fine without try-with-resources.

Also, a big motivator behind try-with-resources is making sure exceptions don't get masked. That's not a consideration so much with executors, all the exception-throwing will be happening in the tasks submitted to the executor, exception-masking is not an issue.

1
wilmol On

ExecutorService is AutoCloseable in Java 19+

Starting from Java 19, ExecutorService implements AutoCloseable.

The default implementation invokes shutdown() and waits for tasks to complete with awaitTermination in a loop. It calls shutdownNow() if interrupted.

Google Guava

For earlier Java, you can use Guava's ForwardingExecutorService to decorate ExeuctorService as AutoCloseable yourself:

class CloseableExecutorService extends ForwardingExecutorService implements AutoCloseable {

  private final ExecutorService delegate;

  CloseableExecutorService(ExecutorService delegate) {
    this.delegate = checkNotNull(delegate);
  }

  @Override
  protected ExecutorService delegate() {
    return delegate;
  }

  @Override
  public void close() {
    // copy paste from JDK 19 EA
    boolean terminated = isTerminated();
    if (!terminated) {
      shutdown();
      boolean interrupted = false;
      while (!terminated) {
        try {
          terminated = awaitTermination(1L, TimeUnit.DAYS);
        } catch (InterruptedException e) {
          if (!interrupted) {
            shutdownNow();
            interrupted = true;
          }
        }
      }
      if (interrupted) {
        Thread.currentThread().interrupt();
      }
    }
  }
}