Search code examples
javacurrencythreadpoolexecutorjava-threads

Why does the execute() method of ExecutorService returned from Executors utility class could not terminate naturally


As we know, the java.util.concurrent.Executors contains many methods such as

  • newCachedThreadPool

  • newFixedThreadPool

  • newScheduledThreadPool

  • newSingleThreadExecutor

  • newSingleThreadScheduledExecutor

    They return ExecutorService, which contains the execute(Runnable task) method. However, when calling the execute(Runnable task) of ExecutorService returned from the aforementioned factory methods, it could only terminate by calling shutdown() or shutdownNow()

For instance, if we add the following code to the main method,

ExecutorService e = Executors.newSingleThreadExecutor();
e.execute(() -> system.out.println("test")); 

the calling the the main program will never terminate as the shutdown() or shutdownNow() is not called. So a program containing the following snippet in main will terminate

ExecutorService e = Executors.newSingleThreadExecutor();
e.execute(() -> system.out.println("test"));
e.shutdown();

However, some subclasses of ExecutorService such as the one returned by calling Executors.newWorkStealingPool or the ForkJoinPool can terminate without calling shutdown() or shutdownNow()

So my QUESTION is: why does the execute() of the ExecutorService returned from the aforementioned factory methods starting with "new" not terminate without calling shutdown() or shutdownNow() from the design point of view?


Solution

  • Briefly about java threads: there are two types of threads - daemon and non-daemon. The program terminates when all of its non-daemon threads have finished execution. Daemon threads can only run as long the program is running and do not block termination, e.g. garbage collector. When a java program starts all of its threads except the main thread are daemon.

    newSingleThreadExecutor() and its defaultThreadFactory() create non-daemon threads. Which kinda makes sense - you're creating a pool of threads that wait for work, it should be your explicit intention to shut it down.

    ForkJoinPool, on the other hand, abstracts you from the underlying thread pool. So it can use daemon threads, as it is generally your intention to wait for task execution anyways.