亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

簡(jiǎn)介 在開發(fā)中使用線程池去執(zhí)行異步任務(wù)是比較普遍的操作,然而雖然有些異步操作我們并不十分要求可靠性和實(shí)時(shí)性,但總歸業(yè)務(wù)還是需要的。如果在每次的服務(wù)發(fā)版過(guò)程中,我們不去介入線程池的停機(jī)邏輯,那么很有可能就會(huì)造成線程池中隊(duì)列的任務(wù)還未執(zhí)行完成,自然就會(huì)造成數(shù)據(jù)的丟失。

探究

注意,本文所有前提是對(duì)進(jìn)程進(jìn)行下線時(shí)使用的是kill -15

我們知道Spring已經(jīng)實(shí)現(xiàn)了自己的優(yōu)雅停機(jī)方案,詳細(xì)請(qǐng)參考o(jì)rg.springframework.context.support.AbstractApplicationContext#registerShutdownHook,然后主要看調(diào)用的org.springframework.context.support.AbstractApplicationContext#doClose, 在這個(gè)方法里定義了容器銷毀的執(zhí)行順序

protected void doClose() {
  // Check whether an actual close attempt is necessary...
  if (this.active.get() && this.closed.compareAndSet(false, true)) {
   if (logger.isDebugEnabled()) {
    logger.debug("Closing " + this);
   }

   LiveBeansView.unregisterApplicationContext(this);

   try {
    // Publish shutdown event.
    publishEvent(new ContextClosedEvent(this));
   }
   catch (Throwable ex) {
    logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
   }

   // Stop all Lifecycle beans, to avoid delays during individual destruction.
   if (this.lifecycleProcessor != null) {
    try {
     this.lifecycleProcessor.onClose();
    }
    catch (Throwable ex) {
     logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
    }
   }

   // Destroy all cached singletons in the context's BeanFactory.
   destroyBeans();

   // Close the state of this context itself.
   closeBeanFactory();

   // Let subclasses do some final clean-up if they wish...
   onClose();

   // Reset local application listeners to pre-refresh state.
   if (this.earlyApplicationListeners != null) {
    this.applicationListeners.clear();
    this.applicationListeners.addAll(this.earlyApplicationListeners);
   }

   // Switch to inactive.
   this.active.set(false);
  }
 }

我們先主要關(guān)注下destroyBeans這個(gè)方法,看bean的銷毀邏輯是什么,然后看到了下面的一個(gè)bean的銷毀順序邏輯,具體方法在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingletons

 private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
 
 public void destroySingletons() {
  if (logger.isTraceEnabled()) {
   logger.trace("Destroying singletons in " + this);
  }
  synchronized (this.singletonObjects) {
   this.singletonsCurrentlyInDestruction = true;
  }

  String[] disposableBeanNames;
  synchronized (this.disposableBeans) {
   disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
  }
  for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
   destroySingleton(disposableBeanNames[i]);
  }

  this.containedBeanMap.clear();
  this.dependentBeanMap.clear();
  this.dependenciesForBeanMap.clear();

  clearSingletonCache();
 }

可以看到最至關(guān)重要的就是一個(gè)屬性disposableBeans,這個(gè)屬性是一個(gè)LinkedHashMap, 因此屬性是有序的,所以銷毀的時(shí)候也是按照某種規(guī)則保持和放入一樣的順序進(jìn)行銷毀的,現(xiàn)在就是要確認(rèn)這個(gè)屬性里到底存的是什么。

經(jīng)過(guò)調(diào)試發(fā)現(xiàn),在創(chuàng)建bean的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中,會(huì)調(diào)用一個(gè)方法org.springframework.beans.factory.support.AbstractBeanFactory#registerDisposableBeanIfNecessary, 在這個(gè)方法中會(huì)調(diào)用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerDisposableBean然后將當(dāng)前創(chuàng)建的bean放入到屬性disposableBeans中,那么現(xiàn)在來(lái)看一下放入的邏輯什么?

相關(guān)代碼貼一下

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
  AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
  if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
    if (mbd.isSingleton()) {
      // Register a DisposableBean implementation that performs all destruction
      // work for the given bean: DestructionAwareBeanPostProcessors,
      // DisposableBean interface, custom destroy method.
      registerDisposableBean(beanName,
                             new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
    }
    else {
      // A bean with a custom scope...
      Scope scope = this.scopes.get(mbd.getScope());
      if (scope == null) {
        throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
      }
      scope.registerDestructionCallback(beanName,
                                        new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
    }
  }
}

org.springframework.beans.factory.support.AbstractBeanFactory#requiresDestruction

 protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
  return (bean != null &&
    (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || (hasDestructionAwareBeanPostProcessors() &&
      DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessors()))));
 }

經(jīng)過(guò)兩個(gè)方法可以看到如果一個(gè)bean的scope是singleton并且這個(gè)bean實(shí)現(xiàn)了org.springframework.beans.factory.DisposableBean這個(gè)接口的destroy()方法,那么就會(huì)滿足條件。

現(xiàn)在可以確定一點(diǎn),如果我們將線程池交給Spring管理,并且實(shí)現(xiàn)它的close方法,就可以在應(yīng)用收到下線信號(hào)的時(shí)候執(zhí)行這個(gè)bean的銷毀方法,那么我們就可以在銷毀方法中寫線程池的停機(jī)邏輯。

我們知道Spring提供了線程池的封裝,在Spring中如果我們要定義線程池一般會(huì)使用org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor以及用于任務(wù)調(diào)度的org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler,先來(lái)簡(jiǎn)單看個(gè)定義ThreadPoolTaskExecutor線程池的例子

@Configuration
public class ThreadConfig {

    @Bean
    public ThreadPoolTaskExecutor testExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("test-shutdown-pool-");
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        threadPoolTaskExecutor.setQueueCapacity(1000);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return threadPoolTaskExecutor;
    }
}

現(xiàn)在來(lái)一下線程池的這個(gè)類結(jié)構(gòu),ThreadPoolTaskExecutor繼承了org.springframework.scheduling.concurrent.ExecutorConfigurationSupport, 實(shí)現(xiàn)了org.springframework.beans.factory.DisposableBean,完整結(jié)構(gòu)如下

public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
  implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
  
}

public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
  implements BeanNameAware, InitializingBean, DisposableBean {
}

從這里就能看到其實(shí)線程池類ThreadPoolTaskExecutor是滿足最開始看到的銷毀條件的,那么現(xiàn)在就來(lái)看下在父類ExecutorConfigurationSupport中定義的destroy()方法,將其中關(guān)鍵部分代碼摘錄下來(lái)

public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
      implements BeanNameAware, InitializingBean, DisposableBean {
  
    private boolean waitForTasksToCompleteOnShutdown = false;

   private long awaitTerminationMillis = 0;
  
    @Nullable
   private ExecutorService executor;
  
    @Override
   public void destroy() {
       shutdown();
   }

    /**
     * Perform a shutdown on the underlying ExecutorService.
     * @see JAVA.util.concurrent.ExecutorService#shutdown()
     * @see java.util.concurrent.ExecutorService#shutdownNow()
     */
    public void shutdown() {
        if (logger.isInfoEnabled()) {
          logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
        }
        if (this.executor != null) {
          if (this.waitForTasksToCompleteOnShutdown) {
            this.executor.shutdown();
          }
          else {
            for (Runnable remainingTask : this.executor.shutdownNow()) {
              cancelRemainingTask(remainingTask);
            }
          }
          awaitTerminationIfNecessary(this.executor);
        }
    }

    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();
          }
        }
    }

    protected void cancelRemainingTask(Runnable task) {
        if (task instanceof Future) {
            ((Future<?>) task).cancel(true);
        }
    }
}

整個(gè)的邏輯還是比較清晰的, 在容器銷毀的時(shí)候會(huì)調(diào)用本地shutdown()方法, 在這個(gè)方法中會(huì)去判斷waitForTasksToCompleteOnShutdown這個(gè)的屬性,如果為true, 則調(diào)用線程池的shutdown()方法,這個(gè)方法并不會(huì)讓線程池立即停止,而是不再接受新的任務(wù)并繼續(xù)執(zhí)行已經(jīng)在隊(duì)列中的任務(wù)。如果為false, 則取消任務(wù)隊(duì)列中的剩余任務(wù)。而這個(gè)屬性的默認(rèn)值為false。因此默認(rèn)是不具備我們需要的功能的。

然而無(wú)論這個(gè)值的屬性最終是否為TRUE,最終都會(huì)調(diào)用方法awaitTerminationIfNecessary(), 線程的停止無(wú)論是shutdown還是shutdownNow都無(wú)法保證線程池能夠停止下來(lái),因?yàn)樾枰浜暇€程池的方法awaitTermination使用,在這個(gè)方法中指定一個(gè)最大等待時(shí)間,則能夠保證線程池最終一定可以被停止下來(lái)。

不知道有沒有注意到一個(gè)細(xì)節(jié),上述所有對(duì)線程池的操作使用的屬性都是private ExecutorService executor;,那么這個(gè)executor是什么時(shí)候賦值的呢?

畢竟我們?cè)趧?chuàng)建bean的時(shí)候是直接new的ThreadPoolTaskExecutor,并沒有去處理這個(gè)屬性。還是看線程池的父類ExecutorConfigurationSupport, 其實(shí)現(xiàn)了接口org.springframework.beans.factory.InitializingBean,在容器初始化完成后有這樣一段代碼

 @Override
 public void afterPropertiesSet() {
  initialize();
 }

 /**
  * Set up the ExecutorService.
  */
 public void initialize() {
  if (logger.isInfoEnabled()) {
   logger.info("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
  }
  if (!this.threadNamePrefixSet && this.beanName != null) {
   setThreadNamePrefix(this.beanName + "-");
  }
  this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
 }

 protected abstract ExecutorService initializeExecutor(
   ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler);

線程池bean在初始化完成后會(huì)調(diào)用父類的afterPropertiesSet方法,上面的代碼已經(jīng)很清晰的說(shuō)明了問題, 最終父類中又定義了抽象方法initializeExecutor(),供子類去具體實(shí)現(xiàn)如果初始化這個(gè)屬性executor, 因?yàn)槲覀冎谰€程池的實(shí)現(xiàn)除了普通的異步任務(wù)線程池ThreadPoolTaskExecutor, 還有基于定時(shí)調(diào)度的線程池ThreadPoolTaskExecutor, 具體實(shí)現(xiàn)這里就不貼出來(lái)了,反正已經(jīng)能夠看出來(lái)這個(gè)屬性是如何被賦值的了,所以上述銷毀時(shí)代碼可以直接使用。

現(xiàn)在整體總結(jié)下來(lái),其實(shí)發(fā)現(xiàn)我們Spring已經(jīng)幫我們實(shí)現(xiàn)了線程池的優(yōu)雅停機(jī)規(guī)則,在接收到停機(jī)信號(hào)時(shí),先拒絕接收新的任務(wù),并繼續(xù)執(zhí)行已經(jīng)接受的任務(wù),在任務(wù)執(zhí)行完成或者到達(dá)最大等待時(shí)間,完成線程池的關(guān)閉。這么一整套邏輯正是我們所需要的,而我們?nèi)绻褂眠@個(gè)邏輯,僅僅需要在配置線程池的時(shí)候指定下上面看到的waitForTasksToCompleteOnShutdown屬性和awaitTerminationMillis屬性。

修改一下上面之前寫的線程池定義代碼, 將waitForTasksToCompleteOnShutdown屬性設(shè)置為true, 并指定awaitTerminationMillis。

@Configuration
public class ThreadConfig {

    @Bean
    public ThreadPoolTaskExecutor testExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("test-shutdown-pool-");
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        threadPoolTaskExecutor.setQueueCapacity(1000);
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskExecutor.setAwaitTerminationSeconds(60);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return threadPoolTaskExecutor;
    }
}

那么到目前這樣處理是不是就沒有問題了呢?

要分情況來(lái)看, 如果按照上述操作,是能夠保證最初預(yù)期目標(biāo)的。線程池會(huì)在接收到下線指令時(shí)停止接受新的任務(wù),并繼續(xù)執(zhí)行隊(duì)列中的未完成的任務(wù),直到任務(wù)完成或者達(dá)到指定的最大等待時(shí)間。

如果任務(wù)是一些不操作其它資源的,只是一些本地計(jì)算或者日志什么之類的,那么任務(wù)不會(huì)出問題。但是如果任務(wù)本身依賴各種數(shù)據(jù)源,比如數(shù)據(jù)庫(kù)、緩存等之類的,那么就會(huì)出現(xiàn)任務(wù)本身會(huì)執(zhí)行,但是卻會(huì)失敗的問題,因?yàn)閿?shù)據(jù)源已經(jīng)早于線程池關(guān)閉了。

那么,能不能控制數(shù)據(jù)源和線程池的銷毀順序呢?在上面我們看到銷毀順序的時(shí)候看到了線程池會(huì)在放入到disposableBeans的原因,其實(shí)數(shù)據(jù)源也是會(huì)被放入到這個(gè)屬性中的,這個(gè)原因和Spring的生命周期無(wú)關(guān),而是另外一個(gè)判斷條件。

之前沒有貼出來(lái)具體代碼,現(xiàn)在來(lái)看下org.springframework.beans.factory.support.AbstractBeanFactory#requiresDestruction方法中的調(diào)用的另外一個(gè)本地方法org.springframework.beans.factory.support.DisposableBeanAdapter#hasDestroyMethod

 public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
  if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
   return true;
  }
  String destroyMethodName = beanDefinition.getDestroyMethodName();
  if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
   return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
     ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
  }
  return StringUtils.hasLength(destroyMethodName);
 }

之前線程池能夠執(zhí)行銷毀流程是因?yàn)閷?shí)現(xiàn)了接口DisposableBean, 而數(shù)據(jù)源則是實(shí)現(xiàn)了另外一個(gè)接口AutoCloseable, 因此數(shù)據(jù)源也是會(huì)執(zhí)行銷毀邏輯的。

現(xiàn)在我們只要介入bean的創(chuàng)建優(yōu)先級(jí)即可, 使用org.springframework.core.annotation.Order注解來(lái)指定線程池創(chuàng)建的高優(yōu)先級(jí),如下。

@Configuration
@Order(value = Ordered.HIGHEST_PRECEDENCE - 10)
public class ThreadConfig {

    @Bean
    @Order(value = Ordered.HIGHEST_PRECEDENCE - 10)
    public ThreadPoolTaskExecutor testExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("test-shutdown-pool-");
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        threadPoolTaskExecutor.setQueueCapacity(2000000);
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskExecutor.setAwaitTerminationSeconds(60);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return threadPoolTaskExecutor;
    }
}

當(dāng)然實(shí)際上的優(yōu)先級(jí)要根據(jù)情況調(diào)整,但是并沒有生效。后來(lái)看到一個(gè)說(shuō)法,org.springframework.core.annotation.Order注解無(wú)法決定bean的創(chuàng)建順序,只能是bean創(chuàng)建完成后的一些業(yè)務(wù)的執(zhí)行時(shí)間。這個(gè)問題沒搞懂,反正Order未生效。那么只能抄他的代碼然后自己實(shí)現(xiàn)了。

該如何處理呢?

自實(shí)現(xiàn)

回到我們最初的代碼org.springframework.context.support.AbstractApplicationContext#doClose, 之前我們一直在看銷毀bean的邏輯,但是其實(shí)我們可以看到在此之前Spring發(fā)布了一個(gè)ContextClosedEvent事件,也就是說(shuō)這個(gè)事件是早于Spring自己的bean銷毀邏輯前面的。

利用這個(gè)機(jī)制,我們可以將線程池的銷毀邏輯抄過(guò)來(lái),并且監(jiān)聽ContextClosedEvent這個(gè)事件,然后在這個(gè)事件里執(zhí)行我們本地自己一些不被Spring管理的線程池的銷毀邏輯,正如前面看到的一樣。

一個(gè)簡(jiǎn)單的例子如下

@Component
@Slf4j
public class ThreadPoolExecutorShutdownDefinition implements ApplicationListener<ContextClosedEvent> {

    private static final List<ExecutorService> POOLS = Collections.synchronizedList(new ArrayList<>(12));

    /**
     * 線程中的任務(wù)在接收到應(yīng)用關(guān)閉信號(hào)量后最多等待多久就強(qiáng)制終止,其實(shí)就是給剩余任務(wù)預(yù)留的時(shí)間, 到時(shí)間后線程池必須銷毀
     */
    private static final long awaitTermination = 60;

    /**
     * awaitTermination的單位
     */
    private static final TimeUnit timeUnit = TimeUnit.SECONDS;


    /**
     * 注冊(cè)要關(guān)閉的線程池
     * 注意如果調(diào)用這個(gè)方法的話,而線程池又是由Spring管理的,則必須等待這個(gè)bean初始化完成后才可以調(diào)用
     * 因?yàn)橐蕾嚨膡@link ThreadPoolTaskExecutor#getThreadPoolExecutor()}必須要在bean的父類方法中定義的
     * 初始化{@link ExecutorConfigurationSupport#afterPropertiesSet()}方法中才會(huì)賦值
     *
     * @param threadPoolTaskExecutor
     */
    public static void registryExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        POOLS.add(threadPoolTaskExecutor.getThreadPoolExecutor());
    }

    /**
     * 注冊(cè)要關(guān)閉的線程池
     * 注意如果調(diào)用這個(gè)方法的話,而線程池又是由Spring管理的,則必須等待這個(gè)bean初始化完成后才可以調(diào)用
     * 因?yàn)橐蕾嚨膡@link ThreadPoolTaskExecutor#getThreadPoolExecutor()}必須要在bean的父類方法中定義的
     * 初始化{@link ExecutorConfigurationSupport#afterPropertiesSet()}方法中才會(huì)賦值
     *
     * 重寫了{(lán)@link ThreadPoolTaskScheduler#initializeExecutor(java.util.concurrent.ThreadFactory, java.util.concurrent.RejectedExecutionHandler)}
     * 來(lái)對(duì)父類的{@link ExecutorConfigurationSupport#executor}賦值
     *
     * @param threadPoolTaskExecutor
     */
    public static void registryExecutor(ThreadPoolTaskScheduler threadPoolTaskExecutor) {
        POOLS.add(threadPoolTaskExecutor.getScheduledThreadPoolExecutor());
    }

    /**
     * 注冊(cè)要關(guān)閉的線程池, 如果一些線程池未交由線程池管理,則可以調(diào)用這個(gè)方法
     *
     * @param executor
     */
    public static void registryExecutor(ExecutorService executor) {
        POOLS.add(executor);
    }

    /**
     * 參考{@link org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#shutdown()}
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        log.info("容器關(guān)閉前處理線程池優(yōu)雅關(guān)閉開始, 當(dāng)前要處理的線程池?cái)?shù)量為: {} >>>>>>>>>>>>>>>>", POOLS.size());
        if (CollectionUtils.isEmpty(POOLS)) {
            return;
        }
        for (ExecutorService pool : POOLS) {
            pool.shutdown();
            try {
                if (!pool.awaitTermination(awaitTermination, timeUnit)) {
                    if (log.isWarnEnabled()) {
                        log.warn("Timed out while waiting for executor [{}] to terminate", pool);
                    }
                }
            }
            catch (InterruptedException ex) {
                if (log.isWarnEnabled()) {
                    log.warn("Timed out while waiting for executor [{}] to terminate", pool);
                }
                Thread.currentThread().interrupt();
            }
        }
    }
}

如果想要本地的線程池實(shí)現(xiàn)優(yōu)雅停機(jī),則直接調(diào)用上述對(duì)應(yīng)的registryExecutor()方法即可,在容器銷毀的時(shí)候自然會(huì)去遍歷執(zhí)行對(duì)應(yīng)邏輯。

做一點(diǎn)補(bǔ)充

我們所謂的優(yōu)雅停機(jī), 必然是需要各方面的一些配合的。因?yàn)橐粋€(gè)應(yīng)用總歸最重要的還是外界的流量,上面只是處理了線程池的問題。

如果是普通的springboot項(xiàng)目, 停機(jī)無(wú)法解決流量繼續(xù)轉(zhuǎn)發(fā)進(jìn)來(lái)的問題, 如Nginx,只要保證發(fā)布時(shí)發(fā)送kill -15的信號(hào)量并且將發(fā)布機(jī)器從nginx負(fù)載中下線。

如果是Dubbo項(xiàng)目,則麻煩一些, 問題其實(shí)和上述類似,由于Dubbo也注冊(cè)了關(guān)閉的鉤子, 則在停機(jī)時(shí)會(huì)同時(shí)存在多個(gè)鉤子要執(zhí)行的問題。如果Spring的一些容器先銷毀了,Dubbo中的一些任務(wù)則無(wú)法繼續(xù)執(zhí)行。

java.lang.Runtime#addShutdownHook, 當(dāng)存在多個(gè)注冊(cè)的關(guān)閉鉤子時(shí),虛擬機(jī)會(huì)以某種未指定的順序并讓它們同時(shí)運(yùn)行。這就是上述問題存在的原因。

public void addShutdownHook(Thread hook) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
      sm.checkPermission(new RuntimePermission("shutdownHooks"));
    }
    ApplicationShutdownHooks.add(hook);
}

Dubbo應(yīng)用則和上面手動(dòng)監(jiān)聽容器銷毀事件的原理類似, 要讓Dubbo的鉤子先執(zhí)行,由于Dubbo已經(jīng)自己注冊(cè)了關(guān)閉鉤子,那么我們的步驟就變成了在Sprign容器啟動(dòng)的時(shí)候先移除掉Dubbo的shutdownHook, 然后再容器銷毀的時(shí)候再添加回來(lái)。

綜合上面線程池的邏輯, 我們還要保證添加Dubbo的shutdownhook的Listener先執(zhí)行并且執(zhí)行完它的停機(jī)邏輯之后才執(zhí)行我們自己寫的處理線程池停機(jī)的Listener,當(dāng)然如果線程池全部交由了Spring管理,自己沒有按照上面去重寫這一塊的邏輯, 則不需要注意這個(gè)問題。

移除和添加Dubbo的shutdownHook的邏輯類似如下.

public class DubboShutdownListener implements ApplicationListener, PriorityOrdered {
    private static final Logger LOGGER = LoggerFactory.getLogger(DubboShutdownListener.class);

    public DubboShutdownListener() {
    }

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartedEvent) {
            Runtime.getRuntime().removeShutdownHook(DubboShutdownHook.getDubboShutdownHook());
            LOGGER.info("Dubbo default shutdown hook has been removed, It will be managed by Spring");
        } else if (event instanceof ContextClosedEvent) {
            LOGGER.info("Start destroy Dubbo Container on Spring close event");
            DubboShutdownHook.getDubboShutdownHook().destroyAll();
            LOGGER.info("Dubbo Container has been destroyed finished");
        }
    }

    public int getOrder() {
        return 0;
    }
}

分享到:
標(biāo)簽:線程 Java
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定