之前写的一个定时任务,频率一秒一次,常执行。
里面的业务代码比较复杂,有DB查询、DB更新和插入、有嵌套循环、还有调用图片处理的程序等等,放服务器上跑,大概40分钟,就吃掉很多内存,占比85%,再跑下去JVM就越界,导致内存溢出,服务器就启动自我保护自动重启了。
然后就开始排查程序,为什么在JAVA自动回收的情况下,还会把服务器玩坏,突然想到了定时任务的机制问题。
Spring的定时器:@Scheduled(cron = "0/1 * * * * ?"),默认是同步的还是异步的?因为业务代码的复杂性,单次执行往往要超过一秒,如果定时器是异步执行的,也就是说我上一次代码还没有跑完,下一次执行就开始了,时间长了,就会白给。如果是同步执行,下一次执行需要等待上一次执行完毕,那就不会出现,按照情况来说,我一开始认为我的内存溢出问题,就是定时器异步导致的。于是我做了实验。
默认测试:
@Service
@Lazy(false)
public class TestController {
@Scheduled(cron = "0/1 * * * * ?") // 间隔1秒
protected void executeTask() {
try{
for (int i = 0; i < 5; i++) {
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.getStackTrace();
}
}
}
如上,我写了个测试代码,循环5次,每次延误1秒,完整执行完代码要5秒,观察打印情况就能得出是异步还是同步的结果。
从结果来看,很不幸,一秒打印一个数字,也就是说默认是同步的,下一次定时任务的开始需要等待上一次任务的执行完成。
在网上查阅了相关资料,我找到了异步执行的方法。
@Service
@Lazy(false)
@EnableAsync
public class TestController {
@Async
@Scheduled(cron = "0/1 * * * * ?") // 间隔1秒
protected void executeTask() {
try{
for (int i = 0; i < 5; i++) {
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.getStackTrace();
}
}
}
其实就加了两个注解,@EnableAsync和@Async。看看这次的打印结果。
1秒打印1次,1次打印5个数,数字也没有按0-5的顺序。知道是异步,可是这样打印的依据是什么呢?
异步也就是说不同线程,涉及到了多线程,那就少不得要线程配置,在spring里面,如果不手动配置线程,那么将使用缺省配置,即spring在配置文件里有一个破产版基础配置,我在我的项目里找到了这部分内容:
<!-- 计划任务配置,用 @Service @Lazy(false)标注类,用@Scheduled(cron = "0 0 2 * * ?")标注方法 -->
<task:executor id="executor" pool-size="10"/>
<task:scheduler id="scheduler" pool-size="20"/>
<task:annotation-driven scheduler="scheduler" executor="executor" proxy-target-class="true"/>
本来两个pool-size都是10,我将scheduler的改成了,20,跑代码试试会不会有变化。PS:maven项目改动配置文件,记得update maven
试过之后发现没有变化,那么我将两个都改成3看一下。还是没有变化,那就是跟这个pool-size没有什么关系。这应该是一个线程池的大小设置。为了搞清楚为什么这样打印,我又做了测试。
@Service
@Lazy(false)
@EnableAsync
public class TestController {
@Async
@Scheduled(cron = "0/1 * * * * ?") // 间隔1秒
protected void executeTask() {
try {
for (int i = 0; i < 5; i++) {
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String dateTime = dateFormat.format(date);
System.out.println("i=" + i + ":" + dateTime);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.getStackTrace();
}
}
}
我把代码写成了这样,打印后面加了一个时间,因为我的这个代码是写在项目其中一个class文件的,不单独执行,而是跟随整个工程在tomcat中启动,启动后自动执行。
但是,定时器并不是要等tomcat完全start up之后才执行,而是在启动过程中扫描到注解就已经开始了,也就是说我整个项目还没起来,但是这个定时器就开始跑了。这就是我加时间的作用,我要看到在启动过程中,什么时候打印了几条。以下是结果:
i=0:2021-03-19 04:31:30
i=0:2021-03-19 04:31:31
i=1:2021-03-19 04:31:31
i=1:2021-03-19 04:31:32
i=0:2021-03-19 04:31:32
i=2:2021-03-19 04:31:32
i=2:2021-03-19 04:31:33
i=1:2021-03-19 04:31:33
i=0:2021-03-19 04:31:33
i=3:2021-03-19 04:31:33
i=2:2021-03-19 04:31:34
i=3:2021-03-19 04:31:34
i=0:2021-03-19 04:31:34
i=1:2021-03-19 04:31:34
i=4:2021-03-19 04:31:34
i=4:2021-03-19 04:31:35
i=3:2021-03-19 04:31:35
i=1:2021-03-19 04:31:35
i=0:2021-03-19 04:31:35
i=2:2021-03-19 04:31:35
从04:31:34这个时间开始,往后就是一直打印5条数据,也是server完全start up之后。
其实这几组数组非常有规律,数量是1-2-3-4-5-5.....,i的值是0-01-102-2103-23014-43102.......各位吴彦祖多看两遍就知道了-。-