案例详情
后台任务引入系统监控,所有客户端配置、代码修改、服务器端配置都做好后,启动任务,任务可以正常处理数据,监控视图也是正确的。
但是当任务结束退出后,发现进程依然被占用。执行多少次任务,就多了多少个进程。尤其在定时任务中问题尤为明显。
错误分析
实际上这是系统监控的一个bug。
comsat对非web应用的监控要求在main函数里加上如下代码段,使用编程方式启动collector线程:
Launcher launcher = new Launcher();
launcher.startup();
在launcher.startup()使用到了Timer实现定时操作(定时将监控数据发送到服务器端):
private static final Timer timer = new Timer();
timer.scheduleAtFixedRate(proxy, firstTime, period);
即使任务已经跑完,进程依然不会终止,因为Timer线程不是守护线程,在main函数执行完后它依然会继续调度执行。
正确用法
方法1:
修改 private static final Timer timer = new Timer();
为 private static final Timer timer = new Timer(true);
设置Timer线程为守护线程。
方法2:
使用ScheduledThreadPoolExecutor代替Timer。
ScheduledThreadPoolExecutor executor
= new ScheduledThreadPoolExecutor(corePoolSize,new DaemonThreadFactory(true))
以上两种方法都是将定时操作设置为守护线程。
推荐使用方法2,自jdk1.5后,scheduledThreadPoolExecutor作为定时任务的调度器,完全可以代替问题多多的Timer。
平台后续会发布comsat新版本,使用方法2解决这个bug(所以目前任务最好暂不引入comsat监控)。
其它说明
1、daemon线程与 non-daemon线程的区别:
daemon线程,就是一种“在背景提供通用性服务”的线程,它并不属于程序本体。因此,当所有non-daemon线程结束生命,程序也就终止了。如果有任何non-daemon线程还在执行,程序(也就是main()的那个线程)就不能终止。
2、Timer与ScheduledThreadPoolExecutor区别
(1)Timer对调度的支持是基于绝对时间的,因此任务对系统时间的改变是敏感的;而ScheduledThreadPoolExecutor支持相对时间。
(2)Timer使用单线程方式来执行所有的TimerTask,如果某个TimerTask很耗时则会影响到其他TimerTask的执行;而ScheduledThreadPoolExecutor则可以构造一个固定大小的线程池来执行任务。
(3)Timer不会捕获由TimerTask抛出的未检查异常,故当有异常抛出时,Timer会终止,导致未执行完的TimerTask不再执行,新的TimerTask也不能被调度;ScheduledThreadPoolExecutor对这个问题进行了妥善的处理,不会影响其他任务的执行。