在开发Spring Boot应用程序时,定时任务是一项常见的需求。Spring Boot提供了@Scheduled
注解,可用于将方法标记为定时任务,并在预定的时间间隔内执行。那么@Scheduled
注解的执行方式是单线程执行,还是多线程执行?@Scheduled
注解的执行方式会不会产生线程不安全的问题?
以下总结@Scheduled
注解的执行方式,并解释它在Spring Boot中的多线程行为。
一:案例演示
新建两个定时任务,一个是每2秒执行一次,一个是每3秒执行一次
@Component
public class ScheduledTest {
@Scheduled(cron = "0/2 * * * * ? ")
public void testTask1(){
Thread thread = Thread.currentThread();
String threadName = thread.getName();
System.out.println(threadName+"-->"+ "testTask1-->"+LocalDateTime.now());
}
@Scheduled(cron = "0/3 * * * * ? ")
public void testTask2(){
Thread thread = Thread.currentThread();
String threadName = thread.getName();
System.out.println(threadName+"-->"+"testTask2-->"+ LocalDateTime.now());
}
}
发现这两个任务的线程是同一个,由此可以推测Spring Boot提供的@Scheduled
注解默认是以单线程方式执行。下面看下源码:Scheduled提供的核心类ScheduledTaskRegistrar、ContextLifecycleScheduledTaskRegistrar
ContextLifecycleScheduledTaskRegistrar 在bean实例化后会调 其父类ScheduledTaskRegistrar提供的scheduleTasks()
方法,并且创建了单例的调度线程池
由此可见,Spring Boot提供的@Scheduled
注解默认是以单线程方式执行。
有的人可能会想会不会是在同一个类中的方法导致结果是公用了同一个线程?不要猜,去验证!
把testTask1和testTask2分别放在不同的类,结果共用同一个线程。
二:在单线程下会产生什么问题?
单线程环境下,如果某个任务处理时间很长,有可能导致其他任务阻塞。testTask1()休眠一小时模拟长时间处理任务,testTask2()一直处于阻塞状态。
@Component
public class ScheduledTest1 {
@Scheduled(cron = "0/2 * * * * ? ")
public void testTask1() throws InterruptedException {
Thread thread = Thread.currentThread();
String threadName = thread.getName();
System.out.println("testTask1 开始执行~");
TimeUnit.HOURS.sleep(1);
System.out.println(threadName + "-->" + "testTask1-->" + LocalDateTime.now());
}
@Scheduled(cron = "0/3 * * * * ? ")
public void testTask2() {
Thread thread = Thread.currentThread();
String threadName = thread.getName();
System.out.println(threadName + "-->" + "testTask2-->" + LocalDateTime.now());
}
}
因此对于这种场景,我们可以考虑把@Scheduled配置成多线程环境下执行,解决@Scheduled在单线程环境下可能产生的问题。
三:@Scheduled支持多线程吗?
既然SchedulingConfigurer默认是创建了单例线程池,那我们是不是可以自定义线程池当做参数传给Scheduled呢?
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
}
}
案例验证代码
@Component
public class ScheduledTest6 {
@Scheduled(cron = "0/2 * * * * ? ")
public void testTask1() throws InterruptedException {
Thread thread = Thread.currentThread();
String threadName = thread.getName();
System.out.println("testTask1 开始执行~");
TimeUnit.HOURS.sleep(1);
System.out.println(threadName + "-->" + "testTask1-->" + LocalDateTime.now());
}
@Scheduled(cron = "0/3 * * * * ? ")
public void testTask2() {
Thread thread = Thread.currentThread();
String threadName = thread.getName();
System.out.println(threadName + "-->" + "testTask2-->" + LocalDateTime.now());
}
}
由截图可知,有5个线程,1、2、5都正常处理testTask2任务,3、4两个线程正在处理testTask1()任务,因此多线程环境下可以解决单线程环境下可能导致的任务阻塞的问题。
更多细节可以关注我的个人主页 www.zhouy.work