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


程序简介:

使用Spring Ioc管理Bean,通过taskExecutor创建了一个后台长期运行的业务线程,xml配置如下:

    
        
        
        
    

    
        ... ...
    
    
    
        
        
    

“taskExecutor”实现类采用的“ThreadPoolTaskExecutor”线程池,而实际上只创建了“wssHeartBeatListenerTask”一个线程,可以不用线程池的实现方法。此处暂且仍沿用此配置。

“wssHeartBeatListenerTask” Bean是实际的业务类,因为要以线程方式运行,因此实现了“Runnable”接口,并且在run方法中循环从ActiveMQ队列取消息并解析。对该循环进行try-catch捕获异常,如果发现ActiveMQ连接异常,在外层增加一个死循环完成重连工作(后面导致线程无法退出的原因)

“wssHeartBeatListener” Bean是一个辅助类,在"init-method"中调用"taskExecutor"的execute方法创建“wssHeartBeatListenerTask”线程。


问题来了:

启动Myeclipse内嵌的Tomcat服务器,观察到“wssHeartBeatListenerTask”开始执行,此时停止Tomcat服务器。

抛出"org.springframework.orm.hibernate4.HibernateSystemException"异常、"javax.jms.JMSException: java.io.InterruptedIOException"异常,"java.lang.IllegalStateException"异常,“InterruptedException”异常。且有时候Tomcat无法正常停止


分析:

先定位异常抛出的条件,"org.springframework.orm.hibernate4.HibernateSystemException"异常只在Tomcat停止时,业务中有数据库操作才会抛出,与数据库的Transaction操作有关。故定位是因为数据库连接先于线程被销毁,线程操作Transaction过程中访问非法资源导致。

再从网络上查找资料,发现"java.lang.IllegalStateException"异常一般是由于Spring的bean已经被销毁后,仍对其访问造成的。

"javax.jms.JMSException: java.io.InterruptedIOException"异常和“InterruptedException”异常一般是由于线程被调用者interrupt所导致。

综上,该线程应该是被taskExecutor或者是Ioc容器interrupt了,究竟是谁interrupt有兴趣可深究。而被interrupt后,抛出异常被代码中的catch捕获。而捕获后,“wssHeartBeatListenerTask”的外层死循环会重新建立ActiveMQ连接,并不会使线程退出,因此线程并没有在捕获interrupt异常后退出,导致web server 虽然关闭,但后台仍有线程运行,会造成内存泄露,有时候会导致Tomcat无法正常停止。

结论:

程序的设计存在问题,没有区别对待异常的处理方法,对interrupt异常,应该使线程退出。

解决方法:

在“wssHeartBeatListenerTask”类中增加exit成员,用来标记是否需要退出。并将exit作为两层循环的判定条件,捕捉到interrupt异常后,置位exit.

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

按此种方法处理后,线程可以正常退出,但是Tomcat仍然有时候不能正常停止。仍会有异常抛出,有些是因为调用log4j记录log但是log4j已经销毁导致。此时已不想再一一分析异常来源,如果线程能够自己主动退出,而不是被别人中断后再响应,岂不是万事大吉?

进一步求解:

于是在网上搜罗各种Tomcat spring退出时的流程,发现了Bean的"destroy-method"属性,对立与''init-method"。于是想到,给“wssHeartBeatListenerTask”增加"destroy-method"方法,在"destroy-method"中置位exit,主动退出,不就可以逃脱直至最后被动中断的厄运了么?

两遭小地雷:

想到就试,一个好的程序员就是要不断地实验,而不是老是想。"destroy-method"建好,exit设为true。结果它仍然给我报异常。why?简单一琢磨,置位exit后,线程仍在业务逻辑中,还没有走到while条件判断,而此时Ioc已经快速完成了别的销毁工作,又来interrupt我的线程了。。。好办,在置位exit后,简单地Thread.sleep延时一下,阻止一下Ioc继续销毁的脚步,让我的线程把这次的业务做完后主动退出~

再试,还抛异常!蒙了。。。打击了我之前的思考。。。再捋一遍,逻辑没有问题啊。再多验证几次,发现是偶发的,观察偶发条件,ActiveMQ中没有数据时,才会抛出异常。原来是JMS的receive没有设置超时时间,如果没有数据,就一直阻塞在那里等,while同样不能知道exit已经变为true了。于是将receive设置超时,且"destroy-method"中的Thread.sleep要比receive超时时间设置略长一些,保证while能够知道exit的变化。

再试,果然不再抛异常了~~~大功告成,不过持久性测试继续验证,看看会不会有别的问题。

总结:

其实有些异常并不是问题,也不一定必须处理到没有异常。但是对于后台线程,如果是被动interrupt,有些资源得不到回收,会有可能造成内存泄露。虽然上述问题出现时,我并没有通过工具具体检查是否会出现内存泄露,但一个好的主动退出机制,总强过被动的方法。可能是之前写C写习惯了,内存不收总觉得不踏实。。。

你可能感兴趣的:(Java,Web开发,tomcat,spring,异常,failed,taskExecutor)