Scheduled多任务动态定时器学习总结

一:解决的问题场景

1常用的场景是基于数据库配置定时任务id、corn表达式,名称、描述等。

本次忽略数据库,

2原理:

核心是两个map

ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures 负责定时任务的取消,关闭,创建,主要是对map中的键值对操作

ConcurrentHashMap<String, CronTask> cronTasks负责接收具体要跑的任务和cron表达式,以及动态刷新cron

3实现:

参考网上例子直接上代码:

import com.example.admin.entity.TaskEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

@RestController
public class StartController {
    @Autowired
    private MyScheduledTask scheduledTask;

    @PostMapping(value = "/startOrChangeCron")
    public String changeCron(@RequestBody List<TaskEntity> list){
        if (!CollectionUtils.isEmpty(list)) {
            // 这里模拟存在数据库的数据
            scheduledTask.refresh(list);
        }
        return "task任务:" + list.toString() + "已经开始运行";
    }

    @PostMapping(value = "/stopCron")
    public String stopCron(@RequestBody List<TaskEntity> list){
        if (!CollectionUtils.isEmpty(list)) {
            // 这里模拟将要停止的cron可通过前端传来
            scheduledTask.stop(list);
        }
        List<String> collect = list.stream().map(TaskEntity::getTaskId).collect(Collectors.toList());
        return "task任务:" + collect.toString() + "已经停止启动";
    }

}

请求入口

import com.example.admin.entity.TaskEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

@Component
public class MyScheduledTask implements SchedulingConfigurer {

    private volatile ScheduledTaskRegistrar registrar;

    private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, CronTask> cronTasks = new ConcurrentHashMap<>();

    @Autowired
    private TaskSolverChooser taskSolverChooser;

    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {

        //设置20个线程,默认单线程,如果不设置的话,不能同时并发执行任务
        registrar.setScheduler(Executors.newScheduledThreadPool(10));
        this.registrar = registrar;
    }

    /**
     * 修改 cron 需要 调用该方法
     */
    public void refresh(List<TaskEntity> tasks){
        //取消已经删除的策略任务
        Set<String> sids = scheduledFutures.keySet();
        for (String sid : sids) {
            if(!exists(tasks, sid)){
                scheduledFutures.get(sid).cancel(false);
            }
        }
        for (TaskEntity TaskEntity : tasks) {
            String expression = TaskEntity.getExpression();
            //计划任务表达式为空则跳过
            if(!StringUtils.hasLength(expression)){
                continue;
            }
            //计划任务已存在并且表达式未发生变化则跳过
            final String taskId = TaskEntity.getTaskId();
            final ScheduledFuture<?> scheduledFuture = scheduledFutures.get(taskId);
            if (scheduledFutures.containsKey(taskId)
                    && cronTasks.get(taskId).getExpression().equals(expression)) {
                continue;
            }
            //如果策略执行时间发生了变化,则取消当前策略的任务
            if(scheduledFutures.containsKey(taskId)){
                scheduledFuture.cancel(false);
                scheduledFutures.remove(taskId);
                cronTasks.remove(taskId);
            }
            //业务逻辑处理
            CronTask task = cronTask(TaskEntity, expression);


            //执行业务
            ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
            cronTasks.put(taskId, task);
            scheduledFutures.put(taskId, future);
        }
    }

    /**
     * 停止 cron 运行
     */
    public void stop(List<TaskEntity> tasks){
        tasks.forEach(item->{
            if (scheduledFutures.containsKey(item.getTaskId())) {
                // mayInterruptIfRunning设成false话,不允许在线程运行时中断,设成true的话就允许。
                scheduledFutures.get(item.getTaskId()).cancel(false);
                scheduledFutures.remove(item.getTaskId());
            }
        });
    }

    /**
     * 业务逻辑处理
     */
    public CronTask cronTask(TaskEntity TaskEntity, String expression) {
        return new CronTask(() -> {
            //每个计划任务实际需要执行的具体业务逻辑
            //采用策略,模式 ,执行我们的job
            taskSolverChooser.getTask(TaskEntity.getTaskId()).HandlerJob();
        }, expression);
    }

    private boolean exists(List<TaskEntity> tasks, String tid){
        for(TaskEntity TaskEntity:tasks){
            if(TaskEntity.getTaskId() == tid){
                return true;
            }
        }
        return false;
    }

    @PreDestroy
    public void destroy() {
        this.registrar.destroy();
    }

核心调度

public interface TaskService {
    void HandlerJob();

    String jobId();
}

具体实现任务接口

@Slf4j
@Component
public class TaskSolverChooser implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private Map<String, TaskService> chooseMap = new HashMap<>(16);



    @PostConstruct
    private void registerToTaskSolver(){
        Map<String, TaskService> taskServiceMap = applicationContext.getBeansOfType(TaskService.class);
        for (TaskService value : taskServiceMap.values()) {
            chooseMap.put(value.jobId(), value);
            log.info("task {} 处理器: {} 注册成功",new Object[]{value.jobId(),value});
        }
    }

    /**
     * 获取需要的job
     */
    public TaskService getTask(String jobId){
        return chooseMap.get(jobId);
    }


    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

基于策略模式的调度选择器

import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

@Service
public class TsTaskServiceJobImpl implements TaskService {
    @Override
    public void HandlerJob() {
        System.out.println("------ts 开始执行---------:"+new Date());

        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  " + Thread.currentThread().getName() + "  任务ts启动");
        try {
            Thread.sleep(5000);//任务耗时10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  " + Thread.currentThread().getName() + "  结束");
    }

    @Override
    public String jobId() {
        return "ts";
    }
}

具体实现任务1

import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

@Service
public class CarTaskServiceJob1Imp implements TaskService {
    @Override
    public void HandlerJob() {
        System.out.println("------car 开始执行---------:"+new Date());

        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  " + Thread.currentThread().getName() + "  任务car启动");
        try {
            Thread.sleep(5000);//任务耗时10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  " + Thread.currentThread().getName() + "  结束");
    }

    @Override
    public String jobId() {
        return "car";
    }
}

具体实现任务2

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TaskEntity {
    /**
     *    * 任务id
     *    
     */
    private String taskId;
    /**
     *    * 任务说明
     *    
     */
    private String desc;
    /**
     *    * cron 表达式
     *    
     */
    private String expression;
}

任务实体类

4、缺点

策略模式会造成子类过多

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值