关于java定时任务timer和ServletContextListener

java多线程 专栏收录该内容
1 篇文章 0 订阅

最近因为业务需求,需要做一个定时汇总数据的功能,因此使用到了ServletContextListener和timer定时器,今天来总结一下这次的踩坑之旅。

程序发布到生产环境后没有产生新的汇总数据,日志上的现象是,两个server同时启动线程,并且同时开始汇总数据(真的是同时,毫秒数是不是一致没注意,但是秒数真的是一样的),没有输出汇总结束的日志,因为生产环境使用websphere,部署模式是单个node两个server,也就是两个server同时使用一个war包,所以我断定是两个相同的程序同时对数据库进行操作(查增改都有)产生了死锁。
核心的破解方法一定是只让其中一个server启动这个线程,为此我想了如下几个方法来控制。
方法一:通过IP区分,但因为都是在一个node上,所以两个server的IP相同,卒
方法二:通过服务端口区分,但因为在ServletContextListener中,不能通过request获取服务端口,而获取容器端口的方法又因为容器不同而不同,websphere在网上这方面的资料几!乎!没!有!方法二又卒
方法三:通过插表判断,新建一张表table,只有一个字段,比如叫flag,varchar类型,在表里预先插入一条数据值为’1’,
使用的SQL: update table set flag=varchar_format(current timestamp,‘YYYY-MM-DD’) where DATE_TIME != varchar_format(current timestamp,‘YYYY-MM-DD’)
然后程序里通过判断返回的更新数量来决定这个server是不是启动timer,更新成了会返回1,不成功返回0(持久层是mybatis)
附上代码:
ServletContextListener

public class TimerListener implements ServletContextListener{
    private Timer timer = null;  
    @Override  
    public void contextInitialized(ServletContextEvent event) {  
    	ServletContext servletContext = event.getServletContext();
    	WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    	DataProcessingService dataProcessingService = webApplicationContext.getBean(DataProcessingService.class);
    	
    	boolean flag = dataProcessingService.timerListenerMark();
    	if(!flag){
    		System.out.println("-------------------------------不执行TimerListener-----------------------------------");
    		return;
    	}
    }  
    @Override  
    public void contextDestroyed(ServletContextEvent event) { 
    }
}

DataProcessingService

@Service
public class DataProcessingServiceImpl implements DataProcessingService {
	@Autowired
    private TimerListenerMarkMapper timerListenerMarkMapper;
	@Override
	public boolean timerListenerMark() {
		boolean flag = false;
		//成功将字段update成当前日期,则返回true以执行timer,否则返回false不执行timer
		int i = timerListenerMarkMapper.updateTimerLisMark();
		if(i > 0){
			flag = true;
		}
		return flag;
	}
}

TimerListenerMarkMapper

	<update id="updateTimerLisMark">
		update TIMERLISTENERMARK
		   set DATE_TIME = varchar_format(current timestamp,'YYYY-MM-DD')
		where DATE_TIME != varchar_format(current timestamp,'YYYY-MM-DD')
	</update>

方法三成功了

本以为这个问题解决了就全解决了,然而,生活不止眼前的苟且,还有对你无穷无尽的虐。
单独执行timer的server日志上依旧只有汇总数据的开始,看不到结束,至此隐隐的感觉上边做的那些事都白干了,于是我就想会不会是线程内程序异常终止了,所以做了个测试,将里面用到的表名改成了没有的表的名字,在本地启动程序,果然现象和生产环境的现象一致,代码:
TimerListener

public class TimerListener implements ServletContextListener{
    private Timer timer = null;  
    @Override  
    public void contextInitialized(ServletContextEvent event) {  
    	ServletContext servletContext = event.getServletContext();
    	WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    	DataProcessingService dataProcessingService = webApplicationContext.getBean(DataProcessingService.class);
        timer = new Timer(true);  
        event.getServletContext().log("定时器已启动");  
        System.out.println("-------------------------------TimerListener executing-----------------------------------");
      //设置执行时间
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);//每天
        //定制每天的11:00:00执行
        calendar.set(year, month, day, 11, 30, 00);
        java.util.Date date = calendar.getTime();
        //每天定时执行一次	
        int period = 1000 * 60 * 60 * 24;//执行周期一天
        timer.schedule(new DataProcessingTask(dataProcessingService), date, period);
    }  
}

DataProcessingTask

public class DataProcessingTask extends TimerTask{
    protected final Log logger = LogFactory.getLog(getClass());  
      
    private static boolean isRunning = false;
    
    private DataProcessingService dataProcessingService;
    
    public DataProcessingTask(DataProcessingService dataProcessingService){
		this.dataProcessingService = dataProcessingService;
	}
	public DataProcessingTask(){
		super();
	}
    @Override
    @Transactional
    public void run() {  
        if (!isRunning) {  
            isRunning = true;  
            logger.info("开始执行任务。");  
            //开始处理数据
            dataProcessingService.processingData();	
            logger.info("任务执行结束。"); 
            isRunning = false;
        } else {  
            logger.info("上一次任务执行还未结束,本次任务不能执行。");  
        }  
  
    }  
}

总结下来就是多线程内有异常了不会输出到日志上,就那么悄无声息的停止
解决办法是在方法上加了异常捕获,修改后的run方法如下:

@Override
    @Transactional
    public void run() {  
        if (!isRunning) {  
            isRunning = true;  
            logger.info("开始执行任务。");  
            //开始处理数据
            try{
            	dataProcessingService.processingData();	
            }catch(Exception e){
            	e.printStackTrace();
            }
            logger.info("任务执行结束。"); 
            isRunning = false;
        } else {  
            logger.info("上一次任务执行还未结束,本次任务不能执行。");  
        }  
    } 

再次打到生产环境运行,这次终于把错误信息打印出来了,结果就是因为生产环境少了一张表,程序异常导致线程终止,将表导给生产环境,程序终于正常运行,至此,填完收工

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

c_z_z

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值