SpringBoot中的定时任务是通过`@Scheduled`注解和`ScheduledTaskRegistrar`类来实现的。首先,我们需要在`Application`类上添加`@EnableScheduling`注解来启用定时任务功能,并通过`@SpringBootApplication`注解排除`DataSourceAutoConfiguration.class`,以避免数据源自动配置影响到我们的定时任务。
```java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
接下来,我们创建一个名为`OtherScheduler`的类,并使用`@Slf4j`和`@Component`注解进行标注。在这个类中,我们定义了两个方法:`print()`和`print5()`,分别用于打印每10秒和每5秒的信息。这两个方法都使用了`@Scheduled`注解,并设置了相应的cron表达式。
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class OtherScheduler {
private static final Logger log = LoggerFactory.getLogger(OtherScheduler.class);
@Scheduled(cron = "0/10 * * * * ?")
public void print() {
log.info("每10S打印一次");
}
@Scheduled(cron = "0/5 * * * * ?")
public void print5() {
log.info("每5S打印一次");
}
}
```
原理分析:当Spring Boot应用启动时,会自动扫描项目中的组件,找到带有`@Scheduled`注解的方法,并将它们添加到一个名为`ScheduledTaskRegistrar`的列表中。这个列表是由`ScheduledAnnotationBeanPostProcessor`类生成的,它负责处理这些注解,并将它们转换为真正的定时任务。当应用程序运行时,调度器会根据cron表达式的规则来触发这些定时任务。
以下是重构后的代码:
```java
public void afterPropertiesSet() {
scheduleTasks();
}
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
private void addScheduledTask(@Nullable ScheduledTask task) {
if (task != null) {
this.scheduledTasks.add(task);
}
}
// 启动任务核心方法
public ScheduledTask scheduleCronTask(CronTask task) {
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
if (scheduledTask == null) {
scheduledTask = new ScheduledTask(task);
newTask = true;
}
if (this.taskScheduler != null) {
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
} else {
addCronTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
```
下面改动主要涉及到线程池数量、新增任务、删除任务、销毁任务四个方面,这些改动都是针对DynamicScheduledTaskRegistrar的。
1. 线程池数量:动态调整线程池的大小,以适应不同的任务需求。当任务数量增加时,线程池会自动扩容;当任务数量减少时,线程池会自动缩容。这样可以确保线程池始终能够高效地运行任务。
2. 新增任务:在DynamicScheduledTaskRegistrar中添加了一个新的方法,用于向线程池中添加新的任务。用户可以通过这个方法轻松地将新任务添加到线程池中,而无需担心线程池的初始化和配置问题。
3. 删除任务:在DynamicScheduledTaskRegistrar中添加了一个新的方法,用于从线程池中删除任务。用户可以通过这个方法轻松地从线程池中删除指定的任务,而无需担心线程池中的其他任务受到影响。
4. 销毁任务:在DynamicScheduledTaskRegistrar中添加了一个新的方法,用于销毁线程池中的任务。用户可以通过这个方法轻松地销毁线程池中的指定任务,而无需担心线程池中的其他任务受到影响。
总之,通过对DynamicScheduledTaskRegistrar进行上述改动,我们可以更好地管理和控制线程池中的任务执行,提高系统的性能和稳定性。
```java
public class DynamicScheduledTaskRegistrar extends ScheduledTaskRegistrar {
private static final Logger log = LoggerFactory.getLogger(DynamicScheduledTaskRegistrar.class);
private final Map
public DynamicScheduledTaskRegistrar() {
super();
// 两种实现方案
// ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
// TaskScheduler taskScheduler = new ConcurrentTaskScheduler(scheduledExecutorService);
// 第二种实现方案
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(8);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("dynamic-scheduled-task-");
taskScheduler.initialize();
this.setScheduler(taskScheduler);
/**
* 新增任务
* @param taskName
* @param cron
* @param runnable
*/
public Boolean addCronTask(String taskName, String cron, Runnable runnable) {
if (scheduledTaskMap.containsKey(taskName)) {
log.error("定时任务[" + taskName + "]已存在,添加失败");
return Boolean.FALSE;
}
CronTask cronTask = new CronTask(runnable, cron);
ScheduledTask scheduledTask = this.scheduleCronTask(cronTask);
scheduledTaskMap.put(taskName, scheduledTask);
log.info("定时任务[" + taskName + "]新增成功");
return Boolean.TRUE;
}
/**
* 删除任务
* @param taskName
*/
public void cancelCronTask(String taskName) {
ScheduledTask scheduledTask = scheduledTaskMap.get(taskName);
if (null != scheduledTask) {
scheduledTask.cancel();
scheduledTaskMap.remove(taskName);
}
log.info("定时任务[" + taskName + "]删除成功");
}
@Override
public void destroy() {
super.destroy();
scheduledTaskMap.values().forEach(ScheduledTask::cancel);
}
}
```
线程池数量问题
在使用DynamicScheduledTaskService时,可能会遇到线程池数量不足的问题。默认情况下,线程池的数量是单线程的,这意味着如果某个任务阻塞时间过长,那么后续的任务将不得不等待,从而导致整个系统的性能下降。为了避免这种情况,我们可以采取以下几种策略:
1. 尽量使用异步任务:通过使用异步任务,我们可以将耗时的操作放到后台线程中执行,从而避免阻塞主线程。这样一来,即使某个任务的执行时间较长,也不会影响到其他任务的执行。
2. 增加线程池的数量:为了提高系统的并发性能,我们可以尝试增加线程池的数量。这样,当有大量的任务需要执行时,线程池可以同时处理更多的任务,从而提高整体的处理速度。需要注意的是,增加线程池的数量可能会带来一定的系统开销,因此需要根据实际需求进行权衡。
总之,为了避免线程池数量不足导致的问题,我们需要根据实际情况选择合适的策略来调整线程池的设置。
```java
import orgslf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class DynamicScheduledTaskService {
private static final Logger log = LoggerFactory.getLogger(DynamicScheduledTaskService.class);
private final DynamicScheduledTaskRegistrar dynamicScheduledTaskRegistrar = new DynamicScheduledTaskRegistrar();
/**
* 新增任务
* @param taskName 任务名称
* @param cron cron表达式
*/
public void add(String taskName, String cron) {
Boolean result = dynamicScheduledTaskRegistrar.addCronTask(taskName, cron, () -> print(taskName));
log.info("定时任务添加结果:" + result);
}
/**
* 取消任务
* @param taskName 任务名称
*/
public void cancel(String taskName) {
dynamicScheduledTaskRegistrar.cancelCronTask(taskName);
}
/**
* 打印任务执行日志
* @param taskName 任务名称
*/
private void print(String taskName) {
log.info(taskName + "开始");
try {
Thread.sleep(9000L);
log.info(taskName + "结束111");
} catch (Exception ex) {
}
log.info(taskName + "结束");
}
}
```
以下是重构后的代码:
```java
@RestController
@RequestMapping(value = "scheduler")
public class SchedulerController {
@Autowired
private DynamicScheduledTaskService dynamicScheduledTaskService;
/**
* 新增任务
* @param taskName 任务名称
* @param cron 定时表达式
* @return 操作结果
*/
@GetMapping(value = "add")
public Object add(@RequestParam String taskName, @RequestParam String cron) {
dynamicScheduledTaskService.add(taskName, cron);
return "SUCCESS";
}
/**
* 删除任务
* @param jobName 任务名称
* @return 操作结果
*/
@GetMapping(value = "cancel")
public Object cancel(@RequestParam String jobName) {
dynamicScheduledTaskService.cancel(jobName);
return "SUCCESS";
}
}
```