异步任务与定时任务

devtools/2025/1/18 22:57:39/

一、异步任务

        基于TaskExecutionAutoConfiguration配置类中,注册的ThreadPoolTaskExecutor线程池对象进行异步任务执行。

(一)手动执行异步任务

在yml中配置线程池参数

spring:            task:execution:pool:core-size: 5  # 核心线程数max-size: 20  # 最大线程数queue-capacity: 1000  # 线程池使用的阻塞队列的最大容量scheduling:pool:size: 10  # 配置了调度任务的线程池:线程数为10 (: 10)

代码示例:

java">@Resource
private ThreadPoolTaskExecutor taskExecutor;@Override
public WebUser doUnameLogin(String username, String password) {// 根据用户名 密码 查询用户信息WebUser webUser = webUserService.getByUname(username);// 判断用户是否存在if (webUser == null) {throw new JavasmException(JavasmExceptionEnum.UserNotExist);}// 判断密码if (!password.equals(webUser.getPassword())) {throw new JavasmException(JavasmExceptionEnum.PasswordError);}/*new Thread(() -> {        }).start();*/// 使用线程池异步执行,避免阻塞主线程,而不是使用new ThreadtaskExecutor.execute(()->{// 增加需求:每天,每个用户只记录一次登录信息SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");String date = simpleDateFormat.format(new Date());// 从redis中查询 当前用户,是否有登录记录String loginLogKey = String.format(RedisKeys.Login_Log_Key, date, webUser.getUid());Object o = redisTemplate.opsForValue().get(loginLogKey);if (o == null) { // 用户当天没有登录记录,存入redis和数据库中// 登录时,需要一起查询webUserInfo的内容// 记录登录信息WebUserLoginLog webUserLoginLog = new WebUserLoginLog();webUserLoginLog.setUid(webUser.getUid());webUserLoginLogService.save(webUserLoginLog);redisTemplate.opsForValue().set(loginLogKey, webUser.getUid(), 1, TimeUnit.DAYS);}});// 登录成功 返回用户信息return webUser;
}
java">@Override
@Transactional
public void doRegister(WebUser webUser) {// TODO:查询邮箱是否已经存在// 添加web_user表  web_user_info表webUserService.save(webUser);if (webUser != null && webUser.getWebUserInfo() != null) {webUser.getWebUserInfo().setUid(webUser.getUid());// 随机分配头像String baseUrl = "http://cd.ray-live.cn/imgs/headpic/pic_%s.jpg";// 随机数:ThreadLocalRandom.current线程安全,顾前不顾后int index = ThreadLocalRandom.current().nextInt(0, 70);webUser.getWebUserInfo().setHeadPic(String.format(baseUrl, index));webUserInfoService.save(webUser.getWebUserInfo());}WebUser newWebUser = webUser.clone();// 这里使用克隆的原因:上面没有给webUserInfo赋值。下面的代码会用到webUserInfo,我们不需要webUserInfo中有值// 先执行上面的代码,然后才会执行下面的代码,不存在线程调用异常的问题// 存入redis// 由于往Redis中添加数据,不属于注册主流程,要放到子线程中/*new Thread(() -> {}).start();*/// 使用线程池异步执行,避免阻塞主线程taskExecutor.execute(()->{// 配置的是数据库添加的默认值,此时的 webUser.getUserInfo() 是没有其他默认属性的// 想获取全部的数据,存入Redis,需要重新查询Integer uid = newWebUser.getUid();WebUserInfo userInfo = webUserInfoService.getById(uid);newWebUser.setWebUserInfo(userInfo);String unamekey = String.format(RedisKeys.User_Uname, newWebUser.getUsername());// 因为RedisTemplate<String, Object> ,所以可以传入webUserredisTemplate.opsForValue().set(unamekey, newWebUser);String uidKey = String.format(RedisKeys.User_Uid, newWebUser.getUid());redisTemplate.opsForValue().set(uidKey, newWebUser);});
}

(二)基于异步注解

启动异步注解识别

java">@Configuration
@MapperScan("com.javaplay.playPal.*.dao")
@EnableAsync // 开启异步注解
public class ServerConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}

在需要异步执行的方法添加@Async注解

java">@Async
public void test(String str){log.info(str);
}

不建议直接在本类中使用,因为异步代码散乱到项目各个类中,不易后期维护,且必须跨类才支持异步注解使用

二、定时任务

        定时任务是基于TaskSchedulingAutoConfiguration配置类中,注册的ThreadPoolTaskScheduler任务调度线程池对象。不论是基于注解还是基于SchedulingConfigurer进行定时任务实现,都需要首先在配置类中启用定时任务。  

(一)启动定时任务 

java">@Configuration
@MapperScan("com.javaplay.playPal.*.dao")
@EnableAsync // 开启异步注解
@EnableScheduling // 开启定时任务注解
public class ServerConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}

是否需要同时使用异步和定时?

  • 如果定时任务不需要异步执行,仅需使用@EnableScheduling和@Scheduled注解即可满足需求。
  • 如果希望定时任务能够异步执行,避免阻塞主线程(例如,避免一个定时任务的执行阻塞另一个定时任务),那么除了@EnableScheduling外,还需要添加@EnableAsync。这样,可以将@Async注解应用到定时任务的方法上,使其异步执行。 

(二)固定的定时任务

在编码阶段,已经固定了定时的逻辑,不能在不停止服务器的情况下更改逻辑。

例如,每天凌晨1点执行某个查询任务,如果想要改为每天凌晨2点,就必须停止服务器,更改定时任务规则,再重新启动服务器。

java">@Component
@Slf4j
public class TestTask {// 希望多久/什么频率, 执行一次/多次 当前的方法// 从第0秒开始,每隔5秒执行1次// cron="秒 分 小时 日期 月份 星期 年"// 星期和日期互斥,不能同时设置,必须有1个是?  年是可以省略的@Scheduled(cron = "0/5 * * * * ?")@Asyncpublic void f1() {log.info("---------------测试f1 ------每隔5秒执行1次");}// 从第10秒开始,每秒执行1次,第20秒的时候终止@Scheduled(cron = "10-20 * * * * ?")@Async // 可选,是否加异步,可自定义public void f2() {log.info("===========f2--从第10秒开始,每秒执行1次,第20秒的时候终止");}
}

java">#经典案例: 
“30 * * * * ?” 每分钟第30秒触发任务
“30 10 * * * ?” 每小时的10分30秒触发任务 
“30 10 1 * * ?” 每天1点10分30秒触发任务 
“30 10 1 20 * ?” 每月20号1点10分30秒触发任务 
“30 10 1 20 10 ? *” 每年10月20号1点10分30秒触发任务 
“30 10 1 20 10 ? 2011” 2011年10月20号1点10分30秒触发任务 
“30 10 1 ? 10 * 2011” 2011年10月每天1点10分30秒触发任务 
“30 10 1 ? 10 SUN 2011” 2011年10月每周日1点10分30秒触发任务 
“15,30,45 * * * * ?” 每分钟的第15秒,30秒,45秒时触发任务 
“15-45 * * * * ?” 15到45秒内,每秒都触发任务 
“15/5 * * * * ?” 每分钟的每15秒开始触发,每隔5秒触发一次 
“15-30/5 * * * * ?” 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次 
“0 0/3 * * * ?” 每小时的第0分0秒开始,每三分钟触发一次 
“0 15 10 ? * MON-FRI” 星期一到星期五的10点15分0秒触发任务 
“0 15 10 L * ?” 每个月最后一天的10点15分0秒触发任务 
“0 15 10 LW * ?” 每个月最后一个工作日的10点15分0秒触发任务 
“0 15 10 ? * 5L” 每个月最后一个星期四的10点15分0秒触发任务 
“0 15 10 ? * 5#3”每个月第三周的星期四的10点15分0秒触发任务
java">@Resource
NewsService newsService;// 同步新闻列表
@Scheduled(cron = "0 0 0/1 * * ?")
@Async
public void f3() {log.info("每天00:00:00开始同步新闻列表,每隔1小时执行1次");newsService.syncNews();
}

(三)可变的定时任务

java">package com.javaplay.playPal.task.runnable;import lombok.extern.slf4j.Slf4j;@Slf4j
@Component
public class TestTask implements Runnable {@Overridepublic void run() {log.info("定时任务,执行了");}
}
java">@Component
@Slf4j
public class NewsTask implements Runnable{@ResourceNewsService newsService;@Overridepublic void run() {log.info("开始同步新闻");newsService.syncNews();}
}
create table sys_task
(id     int auto_increment primary key,name   varchar(255)  null comment '任务名称',clazz  varchar(255)  null comment '执行任务的类',cron   varchar(255)  null comment '定时任务表达式',status int default 0 null comment '状态 0关闭 1开启',ctime  datetime      null comment '创建时间'
);INSERT INTO testdb.sys_task (id, name, clazz, cron, status, ctime)
VALUES (1, '测试定时任务', 'com.javaplay.playPal.task.runnable.TestTask', '0/5 * * * * ?', 0, '2025-01-17 19:50:48');
INSERT INTO testdb.sys_task (id, name, clazz, cron, status, ctime)
VALUES (2, '新闻同步', 'com.javaplay.playPal.task.runnable.NewsTask', '0/10 * * * * ?', 0, '2025-01-17 20:39:14');
java">@Component
@Slf4j
public class JavaTestSchedulingConfigurer implements SchedulingConfigurer {private ScheduledTaskRegistrar scheduledTaskRegistrar;private Map<Integer, ScheduledTask> map = new ConcurrentHashMap<>();@ResourceApplicationContext applicationContext;@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {// 项目启动的时候,就已经调用了这个方法,并且是在启动成功之前调用的// 考虑到有可能有多个定时任务,所以,要创建一个线程池,专门用来存放定时任务// 创建一个包含10个线程的调度线程池executorService。ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);// 使用该线程池创建一个ConcurrentTaskExecutor对象taskExecutor,用于执行定时任务。ConcurrentTaskScheduler taskScheduler = new ConcurrentTaskScheduler(executorService);// 开启注册 定时任务对象scheduledTaskRegistrar.setScheduler(taskScheduler);// 放到全局变量,这样其他方法,就可以调用/使用 参数了this.scheduledTaskRegistrar = scheduledTaskRegistrar;}public boolean regTask(SysTask task) {if (task == null) {return false;}// 任务idInteger id = task.getId();// 执行任务的类String clazz = task.getClazz();// 表达式String cron = task.getCron();// new对象try {Class<?> aClass = Class.forName(clazz);// Object o = aClass.getConstructor().newInstance();Object o = applicationContext.getBean(aClass);Runnable runnable = (Runnable) o;// 执行任务对象CronTask cronTask = new CronTask(runnable, cron);// 任务开始ScheduledTask scheduledTask = this.scheduledTaskRegistrar.scheduleCronTask(cronTask);// 将已经开始的任务对象存入全局,等待停止map.put(id, scheduledTask);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}return true;}public void stop(Integer id) {ScheduledTask scheduledTask = map.get(id);if (scheduledTask != null) {// 停止定时任务scheduledTask.cancel();map.remove(id);}}
}
java">@RestController
@RequestMapping("/task")
public class SysTaskController {/*** 服务对象*/@Resourceprivate SysTaskService sysTaskService;@GetMapping("/start/{id}")public R start(@PathVariable Integer id) {sysTaskService.startTask(id);return R.ok();}@GetMapping("/stop/{id}")public R stop(@PathVariable Integer id) {sysTaskService.stopTask(id);return R.ok();}
}
java">@Service("sysTaskService")
public class SysTaskServiceImpl extends ServiceImpl<SysTaskDao, SysTask> implements SysTaskService {@Resourceprivate JavaTestSchedulingConfigurer javaTestSchedulingConfigurer;@Resourceprivate ThreadPoolTaskExecutor taskExecutor;@Overridepublic void startTask(Integer id) {// 查询任务信息SysTask sysTask = getById(id);// 如果任务注册成功if (javaTestSchedulingConfigurer.regTask(sysTask)) {taskExecutor.execute(() -> {// 修改任务状态sysTask.setStatus(1);updateById(sysTask);});}}@Overridepublic void stopTask(Integer id) {javaTestSchedulingConfigurer.stop(id);// 主要业务的代码,不能放到多线程中// 主要业务执行之后的次要业务,比如说修改状态,添加一些附表的值/修改缓存等,可以放入子线程,用来提高效率taskExecutor.execute(()->{SysTask sysTask = new SysTask();sysTask.setId(id);sysTask.setStatus(0);updateById(sysTask);});}
}

调用/task/start/{id}和/task/stop/{id}接口,就可以操作定时任务启停了。


http://www.ppmy.cn/devtools/151683.html

相关文章

网络安全 | 什么是正向代理和反向代理?

关注&#xff1a;CodingTechWork 引言 在现代网络架构中&#xff0c;代理服务器扮演着重要的角色。它们在客户端和服务器之间充当中介&#xff0c;帮助管理、保护和优化数据流。根据代理的工作方向和用途&#xff0c;代理服务器可分为正向代理和反向代理。本文将深入探讨这两种…

Elasticsearch Python 客户端是否与自由线程 Python 兼容?

作者&#xff1a;来自 Elastic Quentin_Pradet 在这篇文章中&#xff0c;我们将进行一些实验&#xff0c;看看 Python Elasticsearch 客户端是否与新的 Python 3.13 自由线程&#xff08;free-threading&#xff09;版本兼容&#xff0c;其中 GIL 已被删除。 介绍 但首先&…

从零搭建SpringBoot3+Vue3前后端分离项目基座,中小项目可用

文章目录 1. 后端项目搭建 1.1 环境准备1.2 数据表准备1.3 SpringBoot3项目创建1.4 MySql环境整合&#xff0c;使用druid连接池1.5 整合mybatis-plus 1.5.1 引入mybatis-plus1.5.2 配置代码生成器1.5.3 配置分页插件 1.6 整合swagger3&#xff08;knife4j&#xff09; 1.6.1 整…

网络安全 | 防护技术与策略

网络安全 | 防护技术与策略 一、前言二、网络安全防护技术2.1 防火墙技术2.2 加密技术2.3 入侵检测与防范系统&#xff08;IDS/IPS&#xff09;2.4 身份认证技术 三、网络安全策略3.1 网络访问控制策略3.2 数据安全策略3.3 应急响应策略 四、网络安全防护技术与策略的整合与优化…

如何进行域名跳转与域名重定向的综合指南

文章摘取于 Dynadot官方博客内容。 在访问一些商业网站时&#xff0c;我们通常会发现这些平台会将多个域名都指向到同一个内容界面。当然&#xff0c;也存在网站迁移到新域名&#xff0c;旧域名则指向新域名以及其内容页面的情况。 这两者实际上都属于域名跳转的范畴&#xff…

使用 Tailwind CSS 的几点感触

大家好&#xff0c;我是大澈&#xff01; 偶然看到了js前端样式库的排名&#xff0c;Tailwind CSS 以大比例的优势&#xff0c;稳稳占据第一的位置。 对于 Tailwind CSS 我之前用的很少&#xff0c;我一般都是使用自定义原子css写法&#xff0c;感觉更自由更舒服&#xff0c;而…

设置完端口转发后,本机可以ping通公网设备,但公网设备无法ping通本机内网ip

设置端口转发后&#xff0c;本机可以ping通公网设备&#xff0c;但公网设备无法ping通本机内网IP&#xff0c;通常与以下原因有关&#xff1a; 1. 端口转发仅针对特定端口 端口转发的作用&#xff1a;端口转发仅将特定端口的流量&#xff08;如TCP/UDP&#xff09;从公网IP转发…

基于微信小程序的电子点菜系统设计与实现(KLW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…