SoFunction
Updated on 2025-04-07

How to elegantly close ExecutorService in Asynchronous

Core Values

In the field of concurrent programming, Java's ExecutorService (located in packages) is a key interface for thread pool management. As the core component of the Executor framework, it provides developers with:

  • Thread lifecycle management automation
  • Intelligent scheduling of task queues
  • Resource reuse optimization mechanism
  • Asynchronous execution result tracking capability

2. Necessity of shutdown mechanism

Incorrect thread pool shutdown will cause:

  • Memory leak (stuck thread cannot be recycled)
  • The application cannot be terminated normally (non-daemon threads remain active)
  • Inconsistent task status (sudden interruption causes data problems)
  • System resource exhaustion (unlimited thread creation)

() Method Detailed Explanation

3.1 Method Features

void shutdown()

State transition

Set the thread pool status to SHUTDOWN triggers the following behavior:

  • Rejected new task submission (triggered RejectedExecutionHandler)
  • Continue to perform existing tasks:
    • Running tasks
    • queued tasks

Typical application scenarios

ExecutorService executor = (4);
// Submit multiple tasks...();

try {
    if (!(60, )) {
        ("There are still tasks that have not been completed within the time limit");
    }
} catch (InterruptedException e) {
    ().interrupt();
}

3.2 Internal operating mechanism

  • Atomic state update: CAS operation modifies thread pool control status
  • Interrupt idle thread: only interrupt the Worker thread waiting for tasks
  • Queue Consumption Guarantee: Completely handle remaining tasks in BlockingQueue

() Method analysis

4.1 Method definition

List<Runnable> shutdownNow()

State transition

Set the thread pool status to STOP, triggering:

  • Reject new tasks immediately
  • Interrupt all worker threads (whether or not)
  • Clear the task queue and return to the list of unexecuted tasks

4.2 Key points for interrupt processing

();
// Typical return value processingList&lt;Runnable&gt; unprocessed = ();
if (!()) {
    ("throw away{}Unexecuted tasks", ());
}

Task interrupt conditions

It can only be terminated if the task code correctly handles interrupts:

class InterruptibleTask implements Runnable {
    public void run() {
        while (!().isInterrupted()) {
            // Perform interruptible operations            try {
                (1000);
            } catch (InterruptedException e) {
                ().interrupt(); // Reset interrupt status                break;
            }
        }
    }
}

5. Comparative analysis

characteristic shutdown() shutdownNow()
New mission acceptance Reject immediately Reject immediately
Running task processing Wait for completion Try to interrupt
Queue task processing Execute all Clear and return
Return value void Task list not executed
Applicable scenarios Elegantly close Emergency termination
Thread interrupt policy Only interrupt idle threads Force interruption of all threads

6. Best Practice Code Examples

6.1 Standard Close Template

public class GracefulShutdownExample {
    // Define timeout and time units (30 seconds)    private static final int TIMEOUT = 30;
    private static final TimeUnit UNIT = ;

    // Method to execute tasks, receive a task list and submit it to the thread pool for execution    public void executeTasks(List&lt;Runnable&gt; tasks) {
        // Create a fixed-size thread pool with the size of the number of processor cores available in the system        ExecutorService executor = (().availableProcessors());
        
        try {
            // Submit each task in the task list to the thread pool            (executor::submit);
        } finally {
            // After all tasks are submitted, disable the thread pool to receive new tasks and start elegantly closing the thread pool            (); // No new tasks are submitted            try {
                // Wait for the task in the thread pool to complete within the specified timeout. If the timeout does not complete, force the thread pool to be closed                if (!(TIMEOUT, UNIT)) {
                    // If it fails to complete within the timeout, call shutdownNow() to force terminate all active tasks                    List&lt;Runnable&gt; unfinished = ();
                    // Handle unfinished tasks such as logging or resubmitting                    handleUnfinishedTasks(unfinished);
                }
            } catch (InterruptedException e) {
                // If it is interrupted while waiting for termination, restore the interrupt state and force the thread pool to be closed                ().interrupt();
                ();
            }
        }
    }
    
    // Methods to deal with unfinished tasks, here we print the number of unfinished tasks    private void handleUnfinishedTasks(List&lt;Runnable&gt; tasks) {
        // If there are unfinished tasks, print the number of tasks and perform additional processing        if (!()) {
            ("Number of tasks not completed: " + ());
            // You can record logs here, requeuing unfinished tasks, etc.        }
    }

}

Construct thread pool: ()Create a fixed-size thread pool that is the number of processor cores available to the system, which can make it more efficient to utilize CPU resources.

Submit task:use(executor::submit)Submit each task to the thread pool for execution.

Elegantly close thread pool:

  • ()Disable the thread pool to receive new tasks, but still executes the submitted tasks.
  • awaitTermination()Methods are used to wait for all tasks to complete. If the task does not complete after the timeout, callshutdownNow()Force the thread pool to close, stop all running tasks, and return unfinished tasks.

Handling interrupts:If it occurs while waiting for terminationInterruptedException, the thread will restore the interrupt state and force the thread pool to be closed.

Handling unfinished tasks: handleUnfinishedTasks()Methods will handle unfinished tasks, such as logging or requeuing unfinished tasks.

6.2 Enhanced implementation of callbacks

public class EnhancedExecutorManager {
    // Define thread pool object    private final ExecutorService executor;
    // Define timeout and unit    private final long timeout;
    private final TimeUnit unit;

    // Constructor, initialize the thread pool and set the timeout and unit    public EnhancedExecutorManager(int corePoolSize, long timeout, TimeUnit unit) {
        // Create a thread pool with corePoolSize and maximum pool size corePoolSize * 2, maximum idle time 60 seconds         = new ThreadPoolExecutor(
            corePoolSize,                             // Core thread pool size            corePoolSize * 2,                         // Maximum thread pool size            60L, ,                    // Living time of idle thread            new LinkedBlockingQueue&lt;&gt;(1000),          // Use a queue with capacity of 1000 to cache tasks            new CustomThreadFactory(),                // Custom thread factory            new () // When the task cannot be submitted, the caller thread executes the task        );
         = timeout;                     // Set the timeout time         = unit;                           // Set timeout unit    }
    
    // How to close thread pool elegantly    public void shutdown() {
        (); // First try to close the thread pool normally and no longer receive new tasks        
        try {
            // Forced shutdown if the thread pool fails to terminate within the specified timeout            if (!(timeout, unit)) {
                ("Forced termination of thread pool...");
                // Force stop all executing tasks and return to the discarded task list                List&lt;Runnable&gt; droppedTasks = ();
                ("Number of discarded tasks: " + ());
            }
        } catch (InterruptedException e) {
            // If the thread pool shutdown operation is interrupted during waiting, force shutdown and restore the interrupt state immediately            ();
            ().interrupt();
        }
    }
    
    // Custom thread factory class for creating threads    private static class CustomThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1); // Thread pool number, used to generate thread name        private final ThreadGroup group; // Thread group        private final AtomicInteger threadNumber = new AtomicInteger(1); // Thread number        private final String namePrefix; // Thread name prefix    
        CustomThreadFactory() {
            // Get the security manager of the current system. If not, use the thread group of the current thread            SecurityManager s = ();
            group = (s != null) ? () :
                                  ().getThreadGroup();
            // Set the prefix of the thread pool name            namePrefix = "pool-" +
                          () + // increment the thread pool number                         "-thread-";
        }
    
        // Method to create a new thread        public Thread newThread(Runnable r) {
            // Create a new thread, the thread group, name and priority are all set            Thread t = new Thread(group, r,
                                  namePrefix + (),
                                  0); // Default priority and daemon settings            // If the thread is a daemon thread, set it to a non-daemon thread            if (())
                (false);
            // Set thread priority to default            if (() != Thread.NORM_PRIORITY)
                (Thread.NORM_PRIORITY);
            return t; // Return to the newly created thread        }
    }
}

Thread pool initialization:

  • EnhancedExecutorManagerUse of the construction methodThreadPoolExecutorCreate a thread pool, the thread pool size is passedcorePoolSizeParameter passing. The maximum number of threads in a thread pool is twice that of the core threads.
  • LinkedBlockingQueueUsed as a task queue with a size of 1000. If the task volume exceeds the queue capacity, useCallerRunsPolicyA policy, that is, the task is executed by the thread that submits the task.
  • Use customCustomThreadFactoryTo create threads.

Elegantly close thread pool:

  • shutdown()The method is called first()to refuse to accept new tasks and then wait for the thread pool to close within the specified timeout.
  • If the thread pool fails to close normally within the timeout time, thenshutdownNow()Forces closed and discarded unexecuted tasks while outputting the number of discarded tasks.
  • If it occurs while waiting for a shutdownInterruptedException, it will force the thread pool to be closed and the interrupt state will be restored.

Custom thread factory:

  • CustomThreadFactoryBy implementingThreadFactoryInterfaces to define the behavior of creating threads, mainly including thread group, thread name, daemon thread state and thread priority configuration.
  • The name of each thread followspool-number-thread-numberformat. The number of thread pools is incremented, and each thread has its own number.

7. Key points to note

  • Daemon thread problem: The default created is a non-daemon thread and needs to be explicitly closed
  • Interruption policy consistency: The task must implement the correct interrupt handling logic
  • Reject strategy cooperation: Responsible configuration of RejectedExecutionHandler
  • Resource release order: Database connections and other resources should be closed before the thread pool
  • Monitoring mechanism: It is recommended to integrate thread pool monitoring (such as JMX)

8. Advanced application scenarios

Leveled shutdown strategy

public class TieredShutdownManager { 
    // Define a list of thread pools with three priority levels: high priority, medium priority, low priority    private final List&lt;ExecutorService&gt; highPriority; 
    private final List&lt;ExecutorService&gt; normalPriority; 
    private final List&lt;ExecutorService&gt; lowPriority; 
  
    // Public method is used to elegantly close all thread pools    public void gracefulShutdown() { 
        // Close high, medium and low priority thread pools in turn        shutdownTier(highPriority, 10, ); 
        shutdownTier(normalPriority, 30, ); 
        shutdownTier(lowPriority, 60, ); 
    } 
  
    // Private method for elegantly closing thread pools with specified priority    private void shutdownTier(List&lt;ExecutorService&gt; tier, long timeout, TimeUnit unit) { 
        // Perform a shutdown operation on the specified thread pool list        (ExecutorService::shutdown); 
  
        // Perform an operation waiting for termination on each thread pool, specifying the timeout        (executor -&gt; { 
            try { 
                // If the thread pool does not terminate within the timeout time, shutdownNow is called to force close                if (!(timeout, unit)) { 
                    (); 
                } 
            } catch (InterruptedException e) { 
                // If the thread is interrupted while waiting for termination, restore the interrupt state and force the thread pool to be closed                ().interrupt(); 
                (); 
            } 
        }); 
    } 
}

gracefulShutdownThe method closes the thread pool with high, medium and low priority in order of priority.

shutdownTierMethod first tries to close each thread pool normally (callshutdown) and then passawaitTerminationThe method waits for the thread pool to end within the specified time, and if it does not end successfully, it is calledshutdownNowForced to close.

During shutdown, if an interrupt occurs, it will be capturedInterruptedExceptionException, interrupts the current thread, and forces the thread pool to be closed.

9. Performance optimization suggestions

Select a queue policy based on the task type:

  • CPU intensive: bounded queue (ArrayBlockingQueue)
  • IO intensive: unbounded queue (LinkedBlockingQueue)

Monitoring key indicators:

ThreadPoolExecutor executor = (ThreadPoolExecutor) service;
("Number of active threads: " + ());
("Number of tasks completed: " + ());
("Quote Size: " + ().size());

Dynamic adjustment parameters:

(newSize);
(newMaxSize);

10. Summary and Suggestions

According to the official Oracle documentation, the following shutdown process is recommended in most production scenarios:

  • Priority call to shutdown()
  • Set a reasonable awaitTermination timeout
  • Call shutdownNow() if necessary
  • Always process returned unfinished tasks
  • Record a complete shutdown log

The correct choice of closing strategy requires comprehensive consideration:

  • Mission importance level
  • System resource limitations
  • Business Continuity Requirements
  • Data consistency requirements

This is the end of this article about how Java elegantly closes the ExecutorService in Asynchronous. For more related content on Java closing the ExecutorService, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!