java场次时间冲突,解决使用@Scheduled创建任务时无法在同一时间执行多个任务的BUG...

最近在项目中需要任务调度框架,正好springboot集成了一个简单定时调度,而且我们项目功能比较简单就没必要引入Quartz这种比较大型的框架。但是在使用的过程中测试人员发现如果多个任务设计同一时间执行会出现只有一个任务在执行其它任务都无法执行的情况。因为问题比较严重就专门研究了一翻,发现问题还真存在。以下是测试流程:

1.新建个测试类,里面定义2个方法execute1和execute2。两个方法里面没有任何操作只打印当前时间和线程名,为了模拟线上的情况让该方法运行的时候sleep 1秒再结束(业务操作需要耗费一定的时间)。

import org.springframework.scheduling.annotation.Scheduled;

import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component

public class MyScheduled {

@Scheduled(cron = "0/5 * * * * ?")

public void execute1(){

String curName = Thread.currentThread().getName() ;

System.out.println("当前时间:"+LocalDateTime.now()+" 任务execute1对应的线程名: "+curName);

try {

Thread.sleep(1000);

} catch (Exception e) {

e.printStackTrace();

}

}

@Scheduled(cron = "0/5 * * * * ?")

public void execute2(){

String curName = Thread.currentThread().getName() ;

System.out.println("当前时间:"+LocalDateTime.now()+" 任务execute2对应的线程名: "+curName);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

*因为项目使用的是springboot框架为了让定时任务生效需要在类上面加上@EnableScheduling以开启对定时任务的支持

按正常的理解此时运行exect1和execute2打印的线程名应该不一致才对,但是测试的结果让人大跌眼镜。以下是本地的运行结果:

当前时间:2018-08-26T10:53:40.123 任务execute1对应的线程名: pool-1-thread-1

当前时间:2018-08-26T10:53:41.127 任务execute2对应的线程名: pool-1-thread-1

当前时间:2018-08-26T10:53:45.014 任务execute2对应的线程名: pool-1-thread-1

当前时间:2018-08-26T10:53:46.028 任务execute1对应的线程名: pool-1-thread-1

当前时间:2018-08-26T10:53:50.016 任务execute2对应的线程名: pool-1-thread-1

当前时间:2018-08-26T10:53:51.029 任务execute1对应的线程名: pool-1-thread-1

可以发现正如测试同事说的那样,同一时间间隔的2个定时任务(都设置了5秒运行一次)只会运行一个,并且神奇的一点时线程名字是一样的。因此有理由怀疑springboot创建线程的时使用了newSingleThreadExecutor。带着这个疑问,我们只能一步步来debug了,我们首先在execute1方法中打个断点看下调用类和线程池看下是什么情况。

04a641d0fded9005c7e4c921602b6d73.png

通过上图我们可以发现springboot创建的线程池poolSize确实是1,当前活动线程数(activethreads)为1。我们继续往下跟踪,首先我们从@EnableScheduling这个注解开始跟踪。

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Import({SchedulingConfiguration.class})

@Documented

public @interface EnableScheduling {

}

可以看到EnableScheduling是将SchedulingConfiguration这个类实例化并注入到springboot容器中,我们继续跟踪下去看下改配置类执行什么操作。

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//

package org.springframework.scheduling.annotation;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Role;

@Configuration

@Role(2)

public class SchedulingConfiguration {

public SchedulingConfiguration() {

}

@Bean(

name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}

)

@Role(2)

public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {

return new ScheduledAnnotationBeanPostProcessor();

}

}

可以看出里面注入了org.springframework.context.annotation.internalScheduledAnnotationProcessor这个类(*这里发现一个@Role(2)注解,没找到这个注解到底实现什么功能)

然后在springboot的API中查找下这个类到底实现什么功能http://fanyi.baidu.com/transpage?from=auto&to=zh&query=https%3A%2F%2Fdocs.spring.io%2Fspring-framework%2Fdocs%2Fcurrent%2Fjavadoc-api%2Forg%2Fspringframework%2Fscheduling%2Fannotation%2FScheduledAnnotationBeanPostProcessor.html&source=url&ie=utf8&render=1&aldtype=16047(英文不好这里只好使用百度翻译一下)

6a3fc6cb0f64402324ca017afbec25b2.png大概的意思是如果没有指定TaskScheduler则会创建一个单线程的默认调度器。因此问题就清楚了,需要自己创建一个TaskScheduler。立马百度一下,发现很简单

@Bean

public TaskScheduler taskScheduler() {

ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();

taskScheduler.setPoolSize(50);

return taskScheduler;

}

只需要把这一段代码放进启动类即可。我这边为了方便就直接放进MySchedule类里面,修改后的代码为:

import org.springframework.context.annotation.Bean;

import org.springframework.scheduling.TaskScheduler;

import org.springframework.scheduling.annotation.EnableScheduling;

import org.springframework.scheduling.annotation.Scheduled;

import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component

@EnableScheduling

public class MyScheduled {

@Bean

public TaskScheduler taskScheduler() {

ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();

taskScheduler.setPoolSize(50);

return taskScheduler;

}

@Scheduled(cron = "0/5 * * * * ?")

public void execute1(){

String curName = Thread.currentThread().getName() ;

System.out.println("当前时间:"+LocalDateTime.now()+" 任务execute1对应的线程名: "+curName);

try {

Thread.sleep(1000);

} catch (Exception e) {

e.printStackTrace();

}

}

@Scheduled(cron = "0/5 * * * * ?")

public void execute2(){

String curName = Thread.currentThread().getName() ;

System.out.println("当前时间:"+LocalDateTime.now()+" 任务execute2对应的线程名: "+curName);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

我们再次测试下效果:

当前时间:2018-08-26T12:09:50.010 任务execute2对应的线程名: taskScheduler-1

当前时间:2018-08-26T12:09:50.010 任务execute1对应的线程名: taskScheduler-2

当前时间:2018-08-26T12:09:55.001 任务execute1对应的线程名: taskScheduler-3

当前时间:2018-08-26T12:09:55.001 任务execute2对应的线程名: taskScheduler-1

当前时间:2018-08-26T12:10:00.017 任务execute2对应的线程名: taskScheduler-2

当前时间:2018-08-26T12:10:00.018 任务execute1对应的线程名: taskScheduler-4

当前时间:2018-08-26T12:10:05.001 任务execute1对应的线程名: taskScheduler-3

当前时间:2018-08-26T12:10:05.001 任务execute2对应的线程名: taskScheduler-1

发现线程名变了,因此问题得到了完美解决。后来继续debug发现ScheduledTaskRegistrar 里面有这么一行代码

4fc96a8b70beae6b76f8629de8b3d78a.png

可以发现当taskScheduler对象为空时默认创建的是newSingleThreadScheduledExecutor()  至此问题解决。但是还有以下2个问题后续有空需要继续研究

1.springboot为啥默认创建的是newSingleThreadScheduledExecutor?

2.@Role注解到底有什么用处

经过这一折腾,感觉自己对springboot的理解还不够深入,希望在项目中一边踩坑一边研究。后期打算集成quartz+mysql+ZK实现高可用的任务调度,当然这是后话目前还是先把项目做好!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值