如何在springboot中使用定时任务

1. 开启支持——在启动类上加注解

由于Spring Schedule已经包含在spring-boot-starter基础模块中了,所以不需要增加额外的依赖。

@SpringBootApplication
@EnableScheduling     <------这里加注解开启
public class TestApplication {

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}
}
2. 建立任务类——执行周期性的工作
@Component     <------这里要加注解
public class MyWork {

	@Scheduled(fixedRate = 5000)
	public void aWork() {
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
		System.out.println("每隔五秒钟遇见一个美女:" + sdf.format(date));
	}
}
3. 定时任务的两种类型
  • 间隔执行
@Scheduled(fixedRate = 5000) // 表示每隔 5000 毫秒执行一次
public void reportCurrentTime() {
    System.out.println("每隔五秒钟执行一次: " + dateFormat.format(new Date()));
}
  • 定时执行
@Scheduled(cron = "0 30 11 ? * *") // 表示在指定时间执行
public void fixTimeExecution() {
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
    System.out.println("在指定时间 " + sdf.format(new Date()) + "执行");
}

cron表达式的解释如下

4. 自定义线程池
  • springboot默认使用单线程池调度任务。
protected void scheduleTasks() {
    if (this.taskScheduler == null) {
        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }
    //省略...
}

对于多个任务而言,需要增加线程池的线程数量,避免任务都放在一个单线程中串行地执行,导致任务执行时间过长,运行的时效性不能保障。
注意:当方法的执行时间超过任务调度周期时,调度器会在下个周期执行方法内容。

  • 自定义线程池
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }
     
    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(20);
    }
}

当然使定时任务并行执行还可以使用 @EnableAsync 和 @Async 这两个注解。

@Component
@EnableAsync
public class AsyncScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Async
    @Scheduled(fixedDelay = 2000)
    public void reportCurrentTimeWithFixedDelay() {
        try {
            TimeUnit.SECONDS.sleep(3);
            log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
5. Web应用中的启动和关闭问题

我们知道通过spring加载或初始化的Bean,在服务停止的时候,spring会自动卸载(销毁)。但是由于线程是JVM级别的,如果用户在Web应用中启动了一个线程,那么这个线程的生命周期并不会和Web应用保持一致。也就是说,即使Web应用停止了,这个线程依然没有结束(死亡)。

解决方法:

  • 当前对象是通过spring初始化
    spring在卸载(销毁)实例时,会调用实例的destroy方法。通过实现DisposableBean接口覆盖destroy方法实现。在destroy方法中主动关闭线程。
@Component
public class MyTask implements DisposableBean{
    @Override
    public void destroy() throws Exception {
        //关闭线程或线程池
        ThreadPoolTaskScheduler scheduler = (ThreadPoolTaskScheduler)applicationContext.getBean("scheduler");
        scheduler.shutdown();
    }
    //省略...
}
  • 当前对象不是通过spring初始化(管理)
    那么我们可以增加一个Servlet上下文监听器,在Servlet服务停止的时候主动关闭线程。
public class MyTaskListenter implements ServletContextListener{
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        //关闭线程或线程池
    }
    //省略...
}
参考资料
  • https://www.cnblogs.com/skychenjiajun/p/9057379.html?utm_source=tuicool&utm_medium=referral
  • 如果希望动态设定并提交定时任务,可以参考这里
<think>好的,我现在需要帮助用户解决在Spring Boot中消或暂停使用@Scheduled注解的定时任务的问题。根据用户提供的引用内容,我需要综合这些信息并结合之前的回答来给出解决方案。 首先,用户提到的引用[1]和[2]指出,Spring Boot默认的@Scheduled注解不支持动态管理任务,比如暂停或消。这可能意味着用户需要寻找替代方案,或者通过扩展Spring的功能来实现动态控制。引用[3]和[4]提到了使用ThreadPoolTaskScheduler和ScheduledFuture来管理任务,这可能是关键点。 回顾用户之前的问题,他们希望暂停运行中的定时任务,而之前的回答涵盖了Java Timer、ScheduledExecutorService、Quartz和gocron等方法。现在用户特别关注Spring Boot中的解决方案,尤其是使用@Scheduled的情况。需要明确的是,@Scheduled本身不支持动态管理,但可以通过其他机制实现。 接下来,我需要考虑如何结合用户提供的引用来构建解决方案。引用[4]中提到使用ScheduledFuture和ThreadPoolTaskScheduler,这可能允许用户获任务的控制权,从而暂停或消任务。同,引用[3]中的配置类展示了如何配置TaskScheduler,这可能为动态管理任务提供线程池支持。 可能的步骤包括: 1. 配置ThreadPoolTaskScheduler,以便管理任务执行。 2. 使用ScheduledFuture来跟踪任务,从而能够消或重新调度。 3. 创建一个任务注册类,用于存储和管理各个任务的ScheduledFuture实例。 4. 提供方法来消或暂停任务(通过消ScheduledFuture)并重新启动。 同,需要注意用户提到的@Scheduled注解的局限性,因此可能需要采用编程方式注册任务,而不是依赖注解。这样,每个任务都可以被单独控制。 另外,用户可能希望在不重启应用的情况下动态管理任务,所以需要确保任务的状态可以被外部操作,比如通过API调用。这可能需要将任务的管理逻辑封装到服务层,并提供相应的端点。 还需要考虑线程安全和任务状态的持久化,特别是在分布式环境中,但根据当前问题,可能暂不需要涉及分布式场景,除非用户特别提出。 总结来说,解决方案应围绕使用ThreadPoolTaskScheduler和ScheduledFuture来实现任务的动态控制,同提供注册和管理任务的方法,以替代或补充@Scheduled注解的静态特性。</think>在Spring Boot中实现@Scheduled定时任务的动态启停,需要结合$ScheduledFuture$和$ThreadPoolTaskScheduler$进行扩展。以下是具体实现方案: --- ### 一、原生@Scheduled的局限 1. **静态绑定问题** $@Scheduled$注解的任务在应用启动即绑定到Spring容器,**无法直接通过API动态启停**(引用[1][2])。 2. **关键突破口** 通过$ThreadPoolTaskScheduler$和$ScheduledFuture$的组合,可实现任务控制权获(引用[3][4])。 --- ### 二、动态任务控制实现 #### 步骤1:配置线程池调度器 ```java @Configuration public class TaskConfig { @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(Runtime.getRuntime().availableProcessors()); scheduler.setThreadNamePrefix("DynamicScheduler-"); scheduler.setRemoveOnCancelPolicy(true); // 消后立即移除 return scheduler; } } ``` #### 步骤2:创建任务注册中心 ```java @Component public class TaskRegistry { private final Map<String, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>(); @Autowired private ThreadPoolTaskScheduler taskScheduler; // 注册周期性任务 public void registerCronTask(String taskId, Runnable task, String cron) { ScheduledFuture<?> future = taskScheduler.schedule(task, new CronTrigger(cron)); taskMap.put(taskId, future); } // 消任务 public boolean cancelTask(String taskId) { ScheduledFuture<?> future = taskMap.get(taskId); if (future != null) { future.cancel(true); // 立即中断执行中的任务 taskMap.remove(taskId); return true; } return false; } // 暂停任务(通过消+记录状态) public boolean pauseTask(String taskId) { ScheduledFuture<?> future = taskMap.get(taskId); if (future != null && !future.isCancelled()) { future.cancel(false); // 不中断正在执行的任务 return true; } return false; } } ``` #### 步骤3:定义可控制的任务单元 ```java public class ControllableTask implements Runnable { private final String taskId; private final TaskRegistry registry; public ControllableTask(String taskId, TaskRegistry registry) { this.taskId = taskId; this.registry = registry; } @Override public void run() { if (!registry.isTaskActive(taskId)) return; // 状态检查 // 实际业务逻辑 System.out.println("Task " + taskId + " executed at " + new Date()); } } ``` --- ### 三、动态启停操作示例 ```java @RestController @RequestMapping("/tasks") public class TaskController { @Autowired private TaskRegistry taskRegistry; // 启动新任务 @PostMapping("/start") public String startTask(@RequestParam String taskId, @RequestParam String cron) { Runnable task = new ControllableTask(taskId, taskRegistry); taskRegistry.registerCronTask(taskId, task, cron); return "Task started"; } // 停止任务 @PostMapping("/stop") public String stopTask(@RequestParam String taskId) { return taskRegistry.cancelTask(taskId) ? "Stopped" : "Task not found"; } // 暂停任务 @PostMapping("/pause") public String pauseTask(@RequestParam String taskId) { return taskRegistry.pauseTask(taskId) ? "Paused" : "Operation failed"; } } ``` --- ### 四、关键机制解析 1. **状态隔离** 每个任务通过$ScheduledFuture$独立控制,避免全局锁竞争(引用[4])。 2. **优雅终止** `future.cancel(true)`会触发$InterruptedException$,需在任务代码中处理中断: ```java try { // 业务逻辑 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重置中断状态 } ``` 3. **Cron动态更新** 可通过重新注册任务实现Cron表达式变更: ```java public void updateCron(String taskId, String newCron) { cancelTask(taskId); registerCronTask(taskId, new ControllableTask(...), newCron); } ``` --- ### 五、与@Scheduled注解结合 若需保留注解式开发,可通过AOP实现动态开关: ```java @Aspect @Component public class ScheduleControlAspect { @Autowired private TaskRegistry registry; @Around("@annotation(scheduled)") public Object aroundScheduled(ProceedingJoinPoint pjp, Scheduled scheduled) throws Throwable { String taskId = getTaskId(pjp); // 根据方法签名生成唯一ID if (registry.isTaskActive(taskId)) { return pjp.proceed(); } return null; } } ``` --- ### 注意事项 1. **幂等性设计**:任务重启需处理中间状态 2. **持久化存储**:建议将任务配置存入数据库实现状态持久化 3. **监控集成**:通过$ThreadPoolTaskScheduler$的监控接口获线程池运行指标
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值