Advertisement

Spring taskExecutor运行后台线程在Tomcat停止时时主动退出的方法

阅读量:

程序简介:

采用Spring IoC机制来管理Bean,并基于taskExecutor创建了一个长期运行的业务线程配置XML参数如下:

复制代码
     <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

    
         <property name="corePoolSize" value="5"></property>
    
         <property name="maxPoolSize" value="10"></property>
    
         <property name="queueCapacity" value="10"></property>
    
     </bean>
    
  
    
     <bean id="wssHeartBeatListenerTask" class="xxx.WssHeartBeatListenerTask">
    
     ... ...
    
     </bean>
    
     
    
     <bean id="wssHeartBeatListener" class="xxx.WssHeartBeatListener" init-method="wssHeartBeatListenerTaskStart">
    
         <constructor-arg ref="taskExecutor"></constructor-arg>
    
         <property name="wssHeartBeatListenerTask" ref="wssHeartBeatListenerTask"></property>
    
     </bean>

该类采用了名为‘ThreadPoolTaskExecutor’的任务执行器进行功能部署;然而,在实际开发中仅实现了单一的一个‘wssHeartBeatListenerTask’任务执行逻辑;无需依赖任务池相关的操作机制;为了简化配置流程此处暂且仍沿用此配置方案

该Bean被视为一个真实的业务类。因为该Bean必须使用多线程机制运行,并且实现了Runnable接口。在run方法中循环从ActiveMQ队列中消费消息并解析它们。对循环进行了try-catch异常捕获。当发现ActiveMQ连接出现异常时,在外层添加了一个持续的重连机制来处理这些问题

在初始化方法中,“wssHeartBeatListener”Bean通过调用taskExecutor的execute方法来生成“wssHeartBeatListenerTask”线程。

问题来了:

打开内部集成的Tomcat服务器,在此期间触发了该特定任务的初始化流程。随后,在此期间退出该Tomcat服务。

该系统在运行过程中可能会触发以下几种关键错误:组织冲突相关的组织冲突相关异常(如org.springframework.orm.hibernate4.HibernateSystemException)、由javax.jms.JMSException与java.io.InterruptedIOException的组合而成的特定错误以及组织冲突相关的组织冲突相关错误(如java.lang.IllegalStateException)。这些情况通常会导致Tomcat服务无法正常退出。

分析:

首先确定异常触发的条件是什么?该特定异常'org.springframework.orm.hibernate4.HibernateSystemException'仅会在Tomcat停止运行时出现。当业务中涉及数据库操作时才会触发此异常,并与数据库中的事务处理过程相关联。具体原因在于:数据库连接在进程启动前就被释放了

为了获取更多信息,在线查阅相关资料后发现'java.lang.IlLEGALSTATEExceptIon'异常通常是由Spring容器中的Bean实例已被销毁后仍然对其进行访问操作所导致的结果

javax.jms.JavaMessageServiceException(JMSException)与javax.java.ioInterruptedIOException(InterruptedIOException)异常通常是由于线程被调用者的中断事件所引发的。

综上所述:
该线程很可能源于taskExecutor或Ioc容器的interruption。
具体由谁触发interruption尚不清楚;如有兴趣者可进一步探究。
在被interruption之后,
异常随后被捕获于代码中的catch块中。
值得注意的是,
该循环会重新建立ActiveMQ连接至'wssHeartBeatListenerTask',
不会导致该线程终止运行。
因此使得该循环并未在捕捉到interruption异常后终止运行,
从而使得该线程并未在捕捉到interruption异常后终止运行。
这将导致Web服务器虽然已关闭,
但仍需后台运行其他进程,
最终引发内存泄漏问题,
并可能干扰Tomcat的正常退出。

结论:

该程序设计存在缺陷,在异常处理方法上缺乏区分性,在处理interrupt 异常时建议让线程退出

解决方法:

向‘wssHeartBeatListenerTask’类添加成员变量exit用于标识是否需要退出系统或程序流程,并将其作为双重循环的终止条件,在捕获interrupt异常时设置为true。

复制代码
 while(!this.exit) {

    
     ... ...
    
     try {
    
     while(!this.exit) {
    
     ......
    
     }
    
     }catch() {
    
     if (e.getCause() instanceof InterruptedException
    
                         || e.getCause() instanceof InterruptedIOException)
    
             this.exit = true;
    
     }finally {
    
     //Close something
    
     ......
    
     }
    
     ......
    
 }

方法不彻底:

采用此方法处理后, 该线程能够正常退出, 然而时有发生无法自行停止的情况, 并会抛出异常. 其中一些情况是因为使用了log4j进行日志记录, 但因日志记录导致. 此时无需深入排查每一条异常来源, 让该线程具备主动退出的能力, 并非被动等待他人响应.

进一步求解:

于是在网上查阅了Tomcat和Spring退出机制的相关流程后发现,在Bean类中具有'destroy-method'属性与其相对应的'destroy-method'属性。于是想到,在该任务中添加'destroy-method'方法并在此方法中设置退出标志位以主动退出系统。随后考虑在该任务中添加'destroy-method'方法,并在此方法中设置退出标志位以主动退出系统。从而避免了陷入最后阶段被动中断的局面。

两遭小地雷:

想到就行动是成为一位熟练的程序员的关键所在。熟练的程序员应当持续进行测试和验证而不是固守想法。
在构建"destroy-method"时将exit设为true这一操作并未解决问题。
深入分析发现一旦置位exit值后业务逻辑尚未进入循环判断阶段此时Ioc已经迅速完成了对其他对象的销毁工作而在此过程中我们的线程仍在等待条件判断完成。
因此为了保证自身安全应在置位exit后适当延长主线程运行时间以阻塞Ioc继续执行其他销毁操作让其能够完成当前业务流程后再主动退出。

再次尝试后仍会抛出异常!这让我意识到之前的思路存在问题。于是我决定对 receive 设置一个超时机制以便更好地监控 while 循环的状态变化。具体来说 ActiveMQ 中没有数据的情况会触发异常而 JMS 的 receive 方法由于未设置超时时间会在无数据情况下长时间阻塞导致 while 循环无法及时检测到 exit 变量的变化。因此在配置中不仅需要对 receive 设置合理的超时时间还需要在 destroy-method 中将 Thread.sleep 参数设置得略长一些以确保 while 循环能够及时察觉到 exit 的变化从而避免潜在的问题。

再试一次的结果显示不再触发异常。经过测试后取得显著成果。然而,在进行持续性测试时发现可能存在其他潜在问题。

总结:

实际上,并非所有异常都是问题,并非必须彻底消除所有异常。但对于后台线程而言,在被动interrupt的情况下,在某些资源无法回收的情况下,则可能导致内存泄漏。即便在上述情况出现时,并未通过工具具体检测潜在的内存泄露风险。相比之下,在存在此类问题时设立一个主动退出机制则更为稳健。可能源于以往在编写C代码时的习惯思维模式——即认为未完全清理内存便存在不安。

全部评论 (0)

还没有任何评论哟~