异步任务与定时任务
三生有幸呦 2024-08-28 12:33:02 阅读 55
1.异步任务
基于TaskExecutionAutoConfiguration配置类中注册的ThreadPoolTaskExecutor线程池对象进行异步任务执行。
1.1 手工执行异步任务
在yml中配置线程池参数
<code>spring:
task:
execution:
pool:
core-size: 5
max-size: 20
queue-capacity: 1000
@Resource
private ThreadPoolTaskExecutor taskExecutor;
taskExecutor.execute(runnable);
1.2 基于异步注解
启动异步注解识别
@Configuration
@EnableAsync//异步任务注解识别
public class AsyncConfig {
}
在需要异步执行的方法添加@Async注解
@Async
public void test(String str){
log.info(str);
}
不建议直接在本类中使用,因为异步代码散乱到项目各个类中,不易后期维护,切必须跨类才支持异步注解使用。
2.定时任务
基于TaskSchedulingAutoConfiguration配置类中注册的ThreadPoolTaskScheduler任务调度线程池对象。 不论是基于注解还是基于SchedulingConfigurer进行定时任务实现,都需要首先在配置类中启用定时任务。
@Configuration
@EnableAsync//异步任务
@EnableScheduling //定时任务
public class AsyncConfig {
}
2.1 基于注解
@Component
public class WeatherTask {
private static Logger logger = LogManager.getLogger(WeatherTask.class);
//秒 分 小时 日期 月份 星期 年(可选,留空)
// 日期 或 星期,必定有一个是 ?
//这一分钟之内,从第20秒开始执行,每隔5秒执行一次
@Scheduled(cron = "20/5 * * * * ?")
public void test1(){
logger.info("---------------WeatherTask-----------");
}
//一分钟之内,从第10秒开始,到第20秒,每秒执行1次
@Scheduled(cron = "10-20 * * * * ?")
public void test2(){
logger.info("---------------test2-----------");
}
}
以上两个任务是串行任务,在一个线程下执行,因为ThreadPoolTaskScheduler线程池中只有1个核心线程数。要使用并行任务,有如下两种方法
添加@Async注解
@Component
public class WeatherTask {
// ThreadPoolTaskScheduler唯一的1个线程用来解析@Scheduled,进入任务调度;
// 到时间要执行任务方法,使用ThreadPoolTaskExecutor来执行方法。
private static Logger logger = LogManager.getLogger(WeatherTask.class);
@Async
@Scheduled(cron = "20/5 * * * * ?")
public void test1(){
logger.info("---------------WeatherTask-----------");
}
@Async
@Scheduled(cron = "10-20 * * * * ?")
public void test2(){
logger.info("---------------test2-----------");
}
}
扩大ThreadPoolTaskScheduler线程数
在yml中配置线程池参数
spring:
task:
scheduling:
pool:
size: 2
不建议基于注解使用定时任务,因为任务状态不可管理。
2.2基于SchedulingConfigurer任务调度对象
需要定时的类名等存入数据库
通过这个类获取任务并执行
CronTask cronTask = new CronTask(r, taskCron);
ScheduledTask scheduledTask = scheduledTaskRegistrar.scheduleCronTask(cronTask);
tasks.put(id,scheduledTask);
创建数据库表sys_task任务表与dao,service,controller
DROP TABLE IF EXISTS `sys_task`;
CREATE TABLE `sys_task` (
`id` varchar(50) NOT NULL,
`task_name` varchar(50) DEFAULT NULL,
`task_clz` varchar(50) DEFAULT NULL,
`task_cron` varchar(50) DEFAULT NULL,
`task_status` int(11) DEFAULT NULL,
`create_by` varchar(50) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `sys_task` VALUES ('1', '任务1', 'com.javasm.redisdemo.task.SmsTaskRunnable', '1/5 * * * * ?', '0', 'admin', '2023-10-07 17:40:50');
@TableName("sys_task")
public class Task {
@TableId(type = IdType.ASSIGN_ID)
private String id;
private String taskName;
private String taskClz;
private String taskCron;
private Integer taskStatus;
private String createBy;
private String createTime;
}
public interface TaskDao extends BaseMapper<Task> {
}
public interface TaskService extends IService<Task> {
List<Task> getRunningTask();
}
@Service("taskService")
public class TaskServiceImpl extends ServiceImpl<TaskDao, Task> implements TaskService {
@Override
public List<Task> getRunningTask() {
QueryWrapper w = new QueryWrapper();
w.eq("task_status", "0");
return this.list(w);
}
}
从SchedulingConfigurer派生子类,在启动tomcat时,加载sys_task下的任务记录启动
@Component
public class JavasmSchedulerConfiguer implements SchedulingConfigurer {
@Resource
private TaskService taskService;
@Resource
private TaskSchedulingProperties properties;
private ScheduledTaskRegistrar scheduledTaskRegistrar;
private Map<String,ScheduledTask> tasks = new HashMap<>();
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
//自定义线程池大小,把串行任务改为并行任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
TaskScheduler taskScheduler = new ConcurrentTaskScheduler(scheduledExecutorService);
scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
this.scheduledTaskRegistrar=scheduledTaskRegistrar;
List<Task> list = taskService.getRunningTask();
for (Task task : list) {
regisTask(task);
}
}
public boolean regisTask(Task task){
String id = task.getId();
String taskClz = task.getTaskClz();//要求类必须从Runnable接口派生,大家自己改造可以不从此接口派生
String taskCron = task.getTaskCron();
Runnable r = createRunnable(taskClz);
if(r!=null){
CronTask cronTask = new CronTask(r, taskCron);
ScheduledTask scheduledTask = scheduledTaskRegistrar.scheduleCronTask(cronTask);
tasks.put(id,scheduledTask);
return true;
}else{
return false;
}
}
public void cancel(String id){
ScheduledTask scheduledTask = tasks.get(id);
if(scheduledTask!=null){
scheduledTask.cancel();
tasks.remove(id);
}
}
//传入类名,必须从Runnable接口派生,穿件Runnable对象
public Runnable createRunnable(String clzName){
try {
Class<?> clz = Class.forName(clzName);
Constructor<?> constructor = clz.getConstructor();
Object o = constructor.newInstance();
if(o instanceof Runnable)
return (Runnable)o;
else
return null;
} catch (Exception e) {
return null;
}
}
}
提供接口供停止/启用任务
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.javasm.redisdemo.common.AsyncExec;
import com.javasm.redisdemo.common.AxiosResult;
import com.javasm.redisdemo.common.Flags;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("task")
public class TaskController {
@Resource
private TaskService taskService;
@Resource
private JavasmSchedulerConfiguer schedulerConfiguer;
@Resource
AsyncExec asyncExec;
//停止任务
@GetMapping("stop/{id}")
public AxiosResult stopTask(@PathVariable String id){
//停止任务运行
schedulerConfiguer.cancel(id);
//update数据库表任务状态
UpdateWrapper w = new UpdateWrapper();
w.set("task_status", Flags.DISABLED);
w.eq("id",id);
taskService.update(w);
return AxiosResult.suc();
}
//启动任务
@GetMapping("start/{id}")
public AxiosResult startTask(@PathVariable String id){
//注册任务
Task task = taskService.getById(id);
boolean isok = schedulerConfiguer.regisTask(task);
//update数据库表任务状态
if(isok){
UpdateWrapper w = new UpdateWrapper();
w.set("task_status", Flags.OK);
w.eq("id",id);
taskService.update(w);
}
return AxiosResult.suc();
}
//执行一次任务
@GetMapping("one/{id}")
public AxiosResult executeTask(@PathVariable String id){
Task task = taskService.getById(id);
Runnable runnable = schedulerConfiguer.createRunnable(task.getTaskClz());
asyncExec.execute(runnable);
return AxiosResult.suc();
}
}
2.3 认识cron表达式
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义: 秒 分 时 日 月 周 年
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59的整数 | , - * / 四个字符 |
分(Minutes) | 0~59的整数 | , - * / 四个字符 |
小时(Hours) | 0~23的整数 | , - * / 四个字符 |
日期(DayofMonth) | 1~31的整数(但是你需要考虑你月的天数) | ,- * ? / L W C 八个字符 |
月份(Month) | 1~12的整数或者 JAN-DEC | , - * / 四个字符 |
星期(DayofWeek) | 1~7的整数或者 SUN-SAT (1=SUN) | , - * ? / L C # 八个字符 |
年(可选,留空)(Year) | 1970~2099 | , - * / 四个字符 |
,:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
- :表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次。
* :通配。
/ :表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。
?:日与周必定有一个是?。
L :表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
# : 用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
#经典案例:
“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秒触发任务
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。