This is the 16th day of my participation in the August More Text Challenge. For details, see:August is more challenging

preface

Thread pool is always used in our daily projects, and is also a necessary knowledge point in the interview process. However, we rarely pay attention to how to safely shut down threads, which often leads to some problems in the production environment. It is difficult to locate these situations because there is no log print, and we may uniformly attribute them to release exceptions. In fact, this piece of content is very much, elegant release, elegant downtime… Wait.

Thread pool series

Java concurrent programming – Thread pool (1) Java concurrent programming – thread pool source analysis (2) Java concurrent programming -JDK thread pool and Spring thread pool (3)

Case 1.

The example is simple: define a Spring thread pool in which a task calls an order service. The order service is cached and then printed.

1.1 Thread Pool Configuration

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor createThreadPoolTaskExecutor (a) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("Xx Scheduled Task");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        returnexecutor; }}Copy the code

1.2 Order Object and order service

@Data
public class OrderDto {
    private String orderNo;
    private Long goodsId;
}

@Service
public class OrderService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    
    @SneakyThrows
    public void saveOrder(a) {
        OrderDto orderDto = new OrderDto();
        orderDto.setOrderNo("No2021081501");
        orderDto.setGoodsId(1L);
        System.out.println("Start saving order information :");
        stringRedisTemplate.opsForValue().set("No2021081501", JSON.toJSONString(orderDto));
        System.out.println("Saved order information successfully");
        String orderInfo = stringRedisTemplate.opsForValue().get("No2021081501");
        System.out.println("orderInfo:"+ orderInfo); }}Copy the code

1.3 Unit Testing

@Service
public class OrderService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public void saveOrder(a) {
        OrderDto orderDto = new OrderDto();
        orderDto.setOrderNo("No2021081501");
        orderDto.setGoodsId(1L);
        System.out.println("Start saving order information :");
        stringRedisTemplate.opsForValue().set("No2021081501", JSON.toJSONString(orderDto));
        System.out.println("Saved order information successfully");
        String orderInfo = stringRedisTemplate.opsForValue().get("No2021081501");
        System.out.println("orderInfo:"+ orderInfo); }}Copy the code

1.4 Operation Result

To save the order information is broken, no further execution, there is no exception, according to the reason should print the order information.

When the main thread unit test is over, the container bean instance is released, so the link does not exist.

1.5 How to Solve the Problem

The solution is as simple as configuring the parameters when the thread pool is configured

executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
Copy the code

The execution of the test case has been consistent with the expected results.

Principle analysis of thread pool safe shutdown

2.1 Four Ways to terminate a thread

  • Normal operation End
  • Exit the thread using the exit flag
  • The Interrupt method terminates the thread,
  • The stop method terminates the thread (the thread is not safe). You can use thread.stop() to terminate the thread directly, but the stop method is as dangerous as suddenly shutting down the computer instead of shutting it down as normally programmed.

2.2 Shutdown and shutdownNow methods of a thread pool

2.2.1 Shutdown Main process

When calling the thread pool’s shuwdown method,

  1. If a thread is executing a task in the pool, the thread will not be interrupted and will continue executing even if the task is blocked.
  2. If the thread pool is blocked waiting to read from the queue, it will be woken up, but it will continue to determine if the queue is empty. If it is not, it will continue to read from the queue, and if it is empty, the thread exits.

2.2.2 ShutdownNow Main process

Call shutdownNow for the thread pool

1) If the thread is executing in the getTask method, it enters the if statement through the for loop, getTask returns null, and the thread exits. Regardless of whether there are unfinished tasks in the thread pool.

2) If the thread is blocked by executing a task submitted to the thread pool, an error will be reported (if the task does not catch InterruptedException), otherwise the thread will complete the current task and exit by returning null through the getTask method.

Main differences between shutdown and shutdown

The shutdown function simply sets the thread pool status to SHUTWDOWN. Tasks that are being executed will continue, and tasks that are not being executed will be interrupted. ShutdownNow, on the other hand, sets the state of the thread pool to STOP. Tasks that are executing are stopped and tasks that are not being executed are returned.

2.3 Setting the Spring thread pool shutdown

Take a look at the source shutdown method for the Spring thread pool shutdown

public void shutdown(a) {
   if (logger.isDebugEnabled()) {
      logger.debug("Shutting down ExecutorService" + (this.beanName ! =null ? "'" + this.beanName + "'" : ""));
   }
   if (this.executor ! =null) {
      // Is it necessary to wait until all tasks are finished
      if (this.waitForTasksToCompleteOnShutdown) {
         this.executor.shutdown();
      }
      else {
         for (Runnable remainingTask : this.executor.shutdownNow()) { cancelRemainingTask(remainingTask); }}// 
      awaitTerminationIfNecessary(this.executor); }}Copy the code

AwaitTerminationIfNecessary method

private void awaitTerminationIfNecessary(ExecutorService executor) {
   if (this.awaitTerminationMillis > 0) {
      try {
         if(! executor.awaitTermination(this.awaitTerminationMillis, TimeUnit.MILLISECONDS)) {
            if (logger.isWarnEnabled()) {
               logger.warn("Timed out while waiting for executor" +
                     (this.beanName ! =null ? "'" + this.beanName + "'" : "") + " to terminate"); }}}catch (InterruptedException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Interrupted while waiting for executor" +
                  (this.beanName ! =null ? "'" + this.beanName + "'" : "") + " to terminate"); } Thread.currentThread().interrupt(); }}}Copy the code

Last spring the thread pool waitForTasksToCompleteOnShutdown and awaitTerminationMillis need to set up at the same time, otherwise it will not take effect. Of course, if the system is down or operations classmate kill -9 PID what safe shutdown or elegant shutdown is not talked about.

Reference documentation

How to achieve elegant downtime gracefully? ThreadPoolTaskScheduler Graceful shutdown of a thread pool