SpringBoot教程(二十一) | SpringBoot实现单点定时任务之@Scheduled

news2024/9/23 15:28:16

SpringBoot教程(二十一) | SpringBoot实现单点定时任务之@Scheduled

  • 前言
  • 巨坑(@Scheduled任务都用了同一个线程去执行,导致定时任务存在堵塞)
    • 解决办法一:添加自定义的ThreadPoolTaskScheduler配置(为调度配置多个线程)
    • 解决办法二(建议用这个):使用异步包裹定时任务

前言

在Spring Boot项目中使用@Scheduled注解实现定时任务时,你通常不需要额外导入特定的依赖,
因为@Scheduled是Spring框架的核心功能之一,并且Spring Boot会自动配置与调度相关的组件。

@EnableScheduling:Spring框架提供的一个注解,用于启用基于注解的定时任务调度功能。当在Spring的配置类(如使用@Configuration注解的类)上使用@EnableScheduling注解时,Spring会自动配置一个任务调度器(TaskScheduler),负责管理所有带有@Scheduled注解的方法。

@Scheduled:Spring框架中用于定时任务调度的注解。它允许开发者将一个方法标记为定时任务,并配置任务的执行时间间隔或Cron表达式,从而实现在指定时间或按照指定周期自动执行该方法的功能。 除了配置Cron表达式外,还可以通过fixedRate和fixedDelay两种方式设置定时任务,这两种方式可以自行了解。

@Slf4j
@Component
@EnableScheduling
public class DemoTask {
 
    // 每5秒执行一次
    @Scheduled(cron = "0/5 * * * * ? ")
    public void testSchedule1() {
        log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }

    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }
 
}

项目启动后
在这里插入图片描述
是成功且正常执行的
当我把阻塞sleep加进去以后

@Slf4j
@Component
@EnableScheduling
public class DemoTask {
 
    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule1() {
         Thread.sleep(10000);//休眠10秒
        log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }

    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }
 
}

在这里插入图片描述

会发现怎么定时任务2变成间隔20秒执行一次了
是因为会执行这个定时任务使用的线程号ID都是同一个,任务1堵塞了10秒导致影响了后面任务2的执行(说明都用了同一个线程去执行定时任务的,简直巨坑!!!)。

巨坑(@Scheduled任务都用了同一个线程去执行,导致定时任务存在堵塞)

解决办法一:添加自定义的ThreadPoolTaskScheduler配置(为调度配置多个线程)

默认情况下,Spring会尝试在Spring应用上下文中查找一个名为taskScheduler的bean,这个bean必须是TaskScheduler接口的实现。
一旦找到了这个bean,Spring就会使用它来调度所有@Scheduled注解标记的方法。
所以Bean的name不能随便乱写

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;  
  
@Configuration  
public class SchedulerConfig {  
     
    //这个bean名字不要乱改,否则会不生效
    @Bean(name = "taskScheduler")  
    public ThreadPoolTaskScheduler taskScheduler() {  
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();  
        // 设置线程池的核心大小  
        scheduler.setPoolSize(10);  
        // 其他设置,如线程名称前缀等(可选)  
        scheduler.setThreadNamePrefix("wocao-task-");  
        // 初始化调度器  
        scheduler.initialize();  
        return scheduler;  
    }  
}

效果如下:

定时任务1不会堵塞定时任务2了,且 定时任务 都是10秒钟执行一次,不会存在堵塞延迟

在这里插入图片描述

解决办法二(建议用这个):使用异步包裹定时任务

(1)首先配置自定义线程池

@Configuration
public class DemoTheadPoolConfig {

    @Bean(name = "taskExecutor")
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(20);
        //缓冲队列200:用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        //线程活路时间 60 秒
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("demo-thread-");
        //设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

(2) 执行异步操作

方法一:使用工具类CompletableFuture.runAsync

CompletableFuture.runAsync 是 Java 8 引入的一个非常有用的工具,用于异步执行任务
runAsync 方法用于启动一个异步任务,这个任务不返回结果(即返回类型为 void)。

基本用法

runAsync 方法有两种形式:

  1. 无参数版本:使用系统默认的 ForkJoinPool 来异步执行任务。

    CompletableFuture.runAsync(() -> {
        // 这里是异步执行的任务
        System.out.println("异步任务执行中:" + Thread.currentThread().getName());
    });
    

    在这个例子中,runAsync 接收一个 Runnable 函数式接口的实现(即一个不接受参数且不返回结果的 run 方法),并在一个默认的线程池中异步执行这个任务。

  2. 带 Executor 参数版本:允许你指定一个自定义的 Executor 来执行异步任务。

    Executor executor = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
    CompletableFuture.runAsync(() -> {
        // 这里是异步执行的任务
        System.out.println("异步任务执行中,使用自定义线程池:" + Thread.currentThread().getName());
    }, executor);
    

    在这个例子中,runAsync 除了接收一个 Runnable 任务外,还接收一个 Executor 参数,允许你控制异步任务的执行线程。

@Slf4j
@Component
@EnableScheduling
public class DemoTask {

    @Autowired
    @Qualifier("taskExecutor") // 确保使用我们自定义的线程池
    private TaskExecutor taskExecutor; // 注入 TaskExecutor
 
    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule1() {
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(10000); // 休眠10秒,模拟业务场景执行时间
                log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, taskExecutor);
    }

    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        CompletableFuture.runAsync(() -> {
            try {
                log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, taskExecutor);
    }
 
}

效果如下

定时任务1不会堵塞定时任务2了,且 定时任务 都是10秒钟执行一次,不会存在堵塞延迟

在这里插入图片描述

方法二:使用@Async+@EnableAsync

@EnableAsync:开启异步任务
@Async:给希望异步执行的方法标注
一般使用@Async都会指定自定义的线程池
在此处的例子应该写成这样@Async(value = “TaskExecutor”)

@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class DemoTask {
 
    // 每10秒执行一次
    @Async("taskExecutor")//指定自定义的线程池
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule1() {
        try {
            Thread.sleep(10000); // 休眠10秒,模拟业务场景执行时间
            log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

   // 每10秒执行一次
    @Async("taskExecutor")//指定自定义的线程池
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        try {
            log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果如下

定时任务1不会堵塞定时任务2了,且 定时任务 都是10秒钟执行一次,不会存在堵塞延迟

在这里插入图片描述

参考文章:
【1】IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2041721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html+css 实现hover 换背景跳动按钮

前言:哈喽,大家好,今天给大家分享html+css 实现hover 换背景跳动按钮!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、原理解析💡这个按钮hover后,有4个变化:📝1.1…

【C++二分查找】2080. 区间内查询数字的频率

本文涉及的基础知识点 C二分查找 LeetCode2080. 区间内查询数字的频率 请你设计一个数据结构,它能求出给定子数组内一个给定值的 频率 。 子数组中一个值的 频率 指的是这个子数组中这个值的出现次数。 请你实现 RangeFreqQuery 类: RangeFreqQuery(i…

eclipse免安装版64位(专业的Java 开发工具 2018版本)

前言 eclipse是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。 一、下载地址 下载链接:分享文件:eclipse v2018.zip 二、安装步骤 1、下载解压后将ecl…

动手学深度学习(pytorch)学习记录7-线性回归的从零开始实现[学习记录]

注:本代码在jupyter notebook上运行 封面图片来源 1、生成数据集 %matplotlib inline import random import torch from d2l import torch as d2l构造数据集:生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。…

【JavaEE】线程池和定时器

🔥个人主页: 中草药 🔥专栏:【Java】登神长阶 史诗般的Java成神之路 ✏️一.线程池 在Java中,线程池(Thread Pool)是一种用于管理并发线程的机制,它提供了一种创建、复用和管理一组…

【C++】一文掌握C++的四种类型转换 --- static_cast、reinterpret_cast、const_cast、dynamic_cast

当面对两个选择时,抛硬币总能奏效。 并不是因为它总能给出对的答案, 而是在你把它抛在空中的那一秒里。 你突然就知道,你希望的结果是什么了。 --- 曾小贤 《爱情公寓》--- 一文掌握C的四种类型转换 1 C中的类型2 类型转换3 四种类型转换…

一次caffeine引起的CPU飙升问题

背景 背景是上游服务接入了博主团队提供的sdk,已经长达3年,运行稳定无异常,随着最近冲业绩,流量越来越大,直至某一天,其中一个接入方(流量很大)告知CPU在慢慢上升且没有回落的迹象&…

Godot《躲避小兵》实战之创建玩家场景

项目设置完之后,我们就可以开始处理玩家控制的角色。 这里我们将玩家放在一个单独的场景当中,这样做的好处是在游戏的其他部分做出来之前,我们就可以对其进行单独测试。 节点结构 场景是一个节点树结构,因此一个场景需要有一个…

设计模式六大原则之:依赖倒置原则

1. 依赖倒置原则简介 依赖倒置原则(Dependency Inversion Principle, DIP) 是面向对象设计的核心原则之一,由罗伯特马丁(Robert C. Martin)提出,旨在降低类间的依赖度,使之更易于维护和扩展。该原则主张高层模块不应该依赖于底层模块&#x…

江科大/江协科技 STM32学习笔记P23

文章目录 DMA直接存储器存取DMA简介存储器映像DMA框图DMA基本结构存储器到存储器的数据转运ADC扫描模式和DMA配合使用流程 DMA直接存储器存取 DMA简介 DMA进行存储器到存储器的数据转运,比如Flash里的一批数据转运到SRAM里,需要软件触发,使用…

JQuery实现的时间插件源码附注释

HTML页面代码 <!DOCTYPE HTML> <html> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta content="width=device-width, initial-scale=1, maximum-scale=1,user-scalable=no;" name="…

【方案】SRM系统整体设计方案(解决方案+实现源码)

一、项目理解 二、总体解决方案概述 三、业务解决方案详述 四、端到端的采购流程管理 1. 采购计划 2. 采购寻源(招投标、询报价、竞价) 3. 合同管理 4. 订单执行与供应商协同 5. 采购分析与评估 五、支撑流程 1. 物资主数据管理 2. 供应商管理 3. 目录管理 4. 评标专家库管理 5…

grom接入Prometheus,grafana

在同级目录下分别创建 docker-compose.yml&#xff0c;与prometheus.yml 配置文件 version: 3.8services:prometheus:image: prom/prometheuscontainer_name: prometheusports:- "9090:9090" # Prometheus Web UI 端口volumes:- ./prometheus.yml:/etc/prometheus…

opencv-python实战项目八:根据颜色抠出图片中感兴趣区域

文章目录 一&#xff0c;简介二&#xff0c;实现方案三、算法实现步骤3.2 处理颜色蒙版&#xff1a;3.3 取出图片中蒙版对应区域 四&#xff0c;整体代码五&#xff0c;效果&#xff1a; 一&#xff0c;简介 本项目旨在开发一个基于OpenCV的图像处理工具&#xff0c;实现根据颜…

商贸城小程序系统开发制作方案

商贸城作为集批发、零售、展示、交流于一体的综合性商业体。通过商贸城小程序系统促进商家与消费者之间的互动&#xff0c;实现线上线下流量的无缝对接。一、用户需求分析 1、顾客需求&#xff1a; 快速查找店铺信息&#xff1b; 在线浏览商品和服务&#xff1b; 实现线上预约、…

【网站项目】SpringBoot675学生心理压力咨询评判

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Vue启动时报异常 ‘error:03000086:digital envelope routines::initialization error‘

问题描述 启动Vue项目时&#xff0c;突发报如下异常&#xff1a; opensslErrorStack: [error:03000086:digital envelope routines::initialization error,error:0308010C:digital envelope routines::unsupported],library: digital envelope routines,reason: unsupported,…

CentOS7下载与安装 即配置网卡

CentOS 7是什么? CentOS7是基于RHEL的企业级Linux操作系统&#xff0c;引入了Systemd、XFS文件系统和Docker支持。它提供了新的软件包、工具和性能调优选项&#xff0c;同时加强了系统安全和稳定性。总的来说&#xff0c;CentOS7是一个稳定、安全、长期支持的操作系统&#xf…

【wiki知识库】09.欢迎页面展示(浏览量统计)SpringBoot部分

&#x1f34a; 编程有易不绕弯&#xff0c;成长之路不孤单&#xff01; 大家好&#xff0c;我是熊哈哈&#xff0c;这个项目从我接手到现在有了两个多月的时间了吧&#xff0c;其实本来我在七月初就做完的吧&#xff0c;但是六月份的时候生病了&#xff0c;在家里休息了一个月的…

【和可被 K 整除的子数组】python刷题记录

R4-前缀和专题 class Solution:def subarraysDivByK(self, nums: List[int], k: int) -> int:ret0# 存储当前位置的上一个位置的前缀和的余数加上当前位置的值对K的余数pre_mod0dictdefaultdict(int)dict[0]1for i in range(len(nums)):pre_mod(pre_modnums[i])%k# 如果能在…