spring scheduled的动态线程池调度和任务进度的监控

spring scheduled的动态线程池调度和任务进度的监控

这篇文章讲述使用spring自带的轻量级调度系统进行动态任务调度,并基于此自定义实现了任务进度监控的调度。(注:项目中并未依赖QuartZ,基于QuartZ和Spring的动态调度参见: Quartz学习——Spring和Quartz集成详解)

一、spring原生调度api的基础应用

在Spring中如果我们想要实现一个调度任务,只需要简单的如下几行代码即可:

@Component //Spring组件定义
public class OneScheduling {
    //SLF4J日志
    private static final Logger log = LoggerFactory.getLogger(OneScheduling.class);

    @Scheduled(cron = "0/3 * * * * *") //注解定义调度任务的方法,这个方法会在spring的ioc中被封装成一个Runnable对象,如果定义了cron表达式则会进入到CronTask的Runnable中,后面会对调度流程进一步分析
    public void one() {
      log.info("one execute");
    }
}

​ 值得一提的是在Springcron调度中不论是使用线程池调度还是单线程调度(可以在满足JSR-236规范的容器中用org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler作为调度器实现并发,或者继承org.springframework.scheduling.concurrent.ConcurrentTaskScheduler自定义调度器实现更丰富的功能,本文仅讨论在java se规范下的调度情况。),都仅在当前任务执行完成后获取cron的下一次执行时间,拿上面的这个例子来说,0/3 * * * * *代表的是每隔3秒执行一次,可是假设我one这个任务执行的时间是4秒,那么相邻两次任务的执行间隔则会是两个cron周期即6秒执行一次。下文中会穿插说明为什么会这样操作。

二、配置线程池调度及任务的动态控制

2.1 任务的基本配置

​ spring中有一套默认的配置,使得即使在我们没有指定调度器的情况下也可以正常工作起来;默认的调度配置使用单线程调度,所有的任务都必须等待当前任务执行完成才可以继续执行;实现SchedulingConfigurer接口可以拿到ScheduledTaskRegistrar对象,通过ScheduledTaskRegistrar即可进行调度器等配置。

//实现ScheduledTaskRegistrar中的configureTasks方法,设置调度器
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
  //创建一个线程池调度器
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
  //设置线程池容量
        scheduler.setPoolSize(20);
  //线程名前缀
        scheduler.setThreadNamePrefix("task-");
  //等待时常
        scheduler.setAwaitTerminationSeconds(60);
  //当调度器shutdown被调用时等待当前被调度的任务完成
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
  //设置当任务被取消的同时从当前调度器移除的策略
        scheduler.setRemoveOnCancelPolicy(true);
  //设置任务注册器的调度器
        taskRegistrar.setTaskScheduler(scheduler);
    }

2.2 任务自己调度,实现动态添加、取消

​ 前文的操作和配置可以实现任务的基本线程池调度,但是,在实际应用中,这种配置往往不能满足需求,举个例子:假如有一天,业主要求手动启停某些特殊的任务;这个时候即使我们把cron表达式写在了xml文件里,先且不说业主是否有正确修改xml的经验与能力,修改配置之后依然需要重启服务来生效配置,这显然是不合乎情理的。

​ 于是我们对上面的方法进行重写,先提取创建线程池调度器的代码:

    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(20);
        scheduler.setThreadNamePrefix("task-");
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setRemoveOnCancelPolicy(true);
        return scheduler;
    }

​ 创建一个Task类用于保存调度任务信息,方便前台展示:

public class Task {
    private long id;
    private String triggerName;
    private String cron;
    private State state;
    private Date nextExecute;
    public Task(long id, String triggerName, String cron, State state, Date nextExecute) {
        this.id = id;
        this.triggerName = triggerName;
        this.cron = cron;
        this.state = state;
        this.nextExecute = nextExecute;
    }

    public enum State {
        RUN, WAITTING_NEXT, STOP
    }
  //setters and getters
}

​ 在configureTasks中拿到所有spring托管的cron调度任务(此处仅取出cron方式调度的任务,如@Scheduled中不是使用的cron表达式则应该同时取出triggerTaskscronTasksfixedRateTasksfixedDelayTasks 以保证托管任务的完整性),并取出来自己管理:

    //维护一个自增长的任务id
    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();
    //调度器
    private ThreadPoolTaskScheduler taskScheduler;
    //维护一个CronTask和ScheduledFuture的map
    private Map cronTaskScheduledFutureMap;
    //维护一个供用户查看的任务列表
    private List taskList;

    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskScheduler = taskScheduler();
        cronTaskScheduledFutureMap = new HashMap<>();
        taskList = new ArrayList<>();
        taskRegistrar
                .getCronTaskList()//拿到spring托管的所有cron任务
                .forEach(
                        cronTask -> {
                          //手动使用线程池调度器调度这些任务,并保存每一个任务调度的scheduledFuture,在对Future的实现类scheduledFuture中可以实现对任务的取消
                            ScheduledFuture scheduledFuture = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
                          //添加到维护的map中
                            cronTaskScheduledFutureMap.put(cronTask, scheduledFuture);
                          //拿到Runnable信息,提供给用户查看,同时用Runnable的MethodName作为每个任务的唯一标识
                            ScheduledMethodRunnable scheduledMethodRunnable = (ScheduledMethodRunnable) cronTask.getRunnable();

                            taskList.add(new Task(ATOMIC_INTEGER.getAndIncrement(), scheduledMethodRunnable.getMethod().toGenericString(), cronTask.getExpression(), Task.State.WRITTING_NEXT, cronTask.getTrigger().nextExecutionTime(new SimpleTriggerContext())));
                        }
                );
      //设置spring托管的任务列表为空
        taskRegistrar.setCronTasksList(null);
    }
    /**
    *@return 返回当前的任务列表。
    */
    public List getTaskList() {
        return taskList;
    }
//以传入任务的triggerName作为唯一标识编辑已经存在的任务
    public void editTask(Task task) {
        if (taskScheduler == null || cronTaskScheduledFutureMap == null) return;
      //对每一个当前已经参与调度的任务进行判断,取消triggerName与传入Task的triggerName相同的任务
        cronTaskScheduledFutureMap.forEach((cronTask, scheduledFuture) -> {
            if (cronTask.getRunnable() instanceof ScheduledMethodRunnable) {
                ScheduledMethodRunnable scheduledMethodRunnable = (ScheduledMethodRunnable) cronTask.getRunnable();
                String methodName = scheduledMethodRunnable.getMethod().toGenericString();
                if (task.getTriggerName().equals(methodName) && scheduledFuture.cancel(true)) {
                    switch (task.getState()) {
                        case RUN://当前传入的任务要求处于调度状态时,立即启动调度
                            cronTaskScheduledFutureMap.put(cronTask, taskScheduler.schedule(scheduledMethodRunnable, new CronTrigger(task.getCron())));
                            break;
                    }
                }
            }
        });
    }

​ 至此当controller中调用editTask方法时传入参数即可启停任务和管理任务。当然也可以使用repository来管理Task,只需要加入注解@RepositoryEventHandler,并在@HandleAfterCreate中调用scheduleConfig.editTask(task)即可,具体方法参见Spring官方说明。

@RestController
public class TaskController {
    private final ScheduleConfig scheduleConfig;

    @Autowired
    public TaskController(ScheduleConfig scheduleConfig) {
        this.scheduleConfig = scheduleConfig;
    }

    @PostMapping("/task")
    public String task(@RequestBody Task task) {
        scheduleConfig.editTask(task);
        return task.getState().name();
    }

    @GetMapping("/task")
    public List doGet() {
        return scheduleConfig.getTaskList();
    }
}

三、 Spring调度源码解析及自定义注解实现任务进度监控

3.1 入口跟踪

​ 先通过org.springframework.scheduling.annotation.SchedulingConfigurer接口找到调度在Spring中的一个入口,这里借用调度源码分享一个我自己查看源码的方法:

  1. 找到直接可以供我们使用的类或接口,例如本例中的SchedulingConfigurer,使用Idea的FindUsage功能查找所有使用了这个实例化对象的地方。

  2. 发现调用的是一个叫
    org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor 的类,这个类实现了ApplicationContextAwareApplicationListener接口,再结合它的命名,可以判断这个类就是整个调度的入口类。

  3. 在我们的项目中并没有配置这个bean,它是怎么被加入到我们的程序中来的呢?还是利用FindUsage这个功能,可以在同一个包下找到一个叫SchedulingConfiguration的类,这个类拥有注解@Configuration(对Bean进行配置),同时在注释区有说明:This configuration class is automatically imported when using the @EnableScheduling annotation. See @EnableScheduling's javadoc for complete usage details. 意思是只要在程序中使用了@EnableScheduling注解,这个Configuration就会自动被添加进去,而这个Configration所配置的Bean也会被添加到Ioc容器。

3.2 调度的工作流程

  1. 取出@Scheduled注解

​ 入口通过实现ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization方法,对每一个初始化完成的bean进行筛选,并找到每一个bean中使用了@Scheduled注解的所有方法上所有的@Scheduled(一个方法支持多个@Scheduled注解,也可以使用@Schedules来指定多个调度)并封装成一个Map

Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                    new MethodIntrospector.MetadataLookup>() {
                        @Override
                        public Set inspect(Method method) {
                            Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
                                    method, Scheduled.class, Schedules.class);
                            return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
                        }
                    });
  1. 根据@Scheduled包含的信息直接提交到调度器或封装成可重复执行的Runnable对象提交到调度器

    拿到@Scheduled信息中包括两种情况:

    • 需要通过触发器调度的任务:信息中包含有效的cron表达式。

      将拿到的信息封装成CronTask对象,并调用TaskScheduler实例对象的ScheduledFuture schedule(Runnable task, Trigger trigger)方法进行调度。java的concurroent包中没有提供触发器可重复触发的任务,所以Spring将需要调度的任务封装成ReschedulingRunnable对象:

      public ScheduledFuture schedule() {//开始调度
         //使用同步锁保障只能有一个线程拿到下次执行时间并加入执行器的调度
         //所以只要任务被封装成ReschedulingRunnable对象,就意味着这个任务不可以交叉执行
         //每次调用TaskScheduler的schedule方法都会创建一个ReschedulingRunnable对象
         //不同的ReschedulingRunnable对象之间在线程池够用的情况下是不会相互影响的,也就是说满足线程池的条件下,TaskScheduler的schedule方法的多次调用是可以交叉执行的
          synchronized (this.triggerContextMonitor) {
              this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
              if (this.scheduledExecutionTime == null) {
                  return null;
              }
              long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
             //这里交给执行器执行的Runnable对象是this,这句话可以理解为要求执行器在到达执行时间的时候执行当前类中的run方法,而在下文的run方法中又调用了schedule方法,这就实现了任务的重复调度。
              this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
              return this;
          }
      }
      
      @Override
      public void run() {
          Date actualExecutionTime = new Date();
          super.run();
          Date completionTime = new Date();
          synchronized (this.triggerContextMonitor) {
              this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
              if (!this.currentFuture.isCancelled()) {
                  schedule();
              }
          }
      }

    • 可以直接调度的任务:信息中包含有效fixedDelayfixedRate数据。

      这类任务都在jdk的concurrent包中提供了原生调度方法,可以直接交给jdk处理。

3.3 定制完全可控的任务调度

​ 声明:本文均以ThreadPoolTaskScheduler或单线程调度器为基础进行测试,根据spring的文档中有提到的这一段:

ConcurrentTaskExecutor

使用ConcurrentTaskExecutor执行器及ConcurrentTaskScheduler可以实现任务调度的管理,希望看到这篇文章的好心人能在文章末尾留言ConcurrentTaskScheudlerDefaultManagedTaskScheduler的使用指南。

3.3.1 确定思路

​ 要实现任务的动态增删改查,最重要的就是要能拿到当前执行的任务,并且,我们还必须拥有对当前任务的管理权限,当任务被提交到执行器之后,jdk就接管了任务,代码中能拿到的就只有这个ScheduledFuture对象,利用这个对象我们可以实现对任务的取消,但是增改查都不能完成。重要的是在spring帮我们实现好的入口类ScheduledAnnotationBeanPostProcessor中,没有把ScheduledFuture通过任何形式暴露出来,因此实现这些功能只能自己维护一个任务的集合,同时这样才能在任务状态发生改变时或者用户改变任务属性时通知到执行器和用户双方。

  1. 继承ScheduledAnnotationBeanPostProcessor并维护一个任务集;
  2. 定义@ProgressScheduled@ProgressSchedules注解以区分官方@Scheduled注解,和传递更多自定义的信息;
  3. 需要一个用来保存任务信息的类Task
  4. 监听任务进度的接口ProgressListener、发送任务执行情况的接口TaskUpdateListener
  5. 继承自ScheduledMethodRunnable调用Method.invoke的Runnable类ProgressScheduledMethodRunnable
  6. 包配置类ProgressSchedulingConfiguration替换ScheduledAnnotationBeanPostProcessor的beanProgressScheduledAnnotationBeanPostProcessor装载到其他spring管理的包中,使用者只需要加入你的依赖而不需要包含你的源代码。
  7. 启用ProgressSchedulingConfiguration的注解@EnableProgressScheduling(使用者只需要像原生spring一样在Configuration标注的类上同时标注EnableProgressScheduling即可激活配置)。

3.3.2 注解驱动

​ 从注解驱动开始一是由于注解不依赖于其他部分,二来注解也是基础。

  1. @ProgressScheduled,在@Scheduled的基础上增加了name属性。
  2. @ProgressSchedules作为@ProgressScheduled的集合。

3.3.3 接口定义

ProgressListener 接口和TaskUpdateListener接口都定义为@FunctionalInterface

/**ProgressListener
    在使用@ProgressScheduled注解时,必须在方法的参数中传入一个ProgressListener的对象,并在适当的时候调用progress(p)方法更新进度。
    这样做有一个好处就是在每次调度器执行被调度的方法时,都可以单独处理本次调度的进度问题,如果仅仅是在有@ProgressScheduled注解的类中加入一个监听器,那么得到的执行进度结果将会在同一个方法拥有多次调度的情况下不再准确,而且也会在同一个类中有多个调度方法的情况下十分杂乱,无法区分。
*/
@FunctionalInterface
public interface ProgressListener {
    void progress(int p);
}
//TaskUpdateListener
@FunctionalInterface
public interface TaskUpdateListener {
    void onUpdate(Task task);
}

3.3.4 ProgressScheduledMethodRunnable的实现

​ 原生ScheduledMethodRunnable仅支持无参调度方法的调用,如果我们想要在适当的地方更新进度,则无法做到。

public class ProgressScheduledMethodRunnable extends ScheduledMethodRunnable {

    private final ProgressListener progressListener;

    public ProgressScheduledMethodRunnable(Object target, Method method, ProgressListener progressListener) {
        super(target, method);
        this.progressListener = progressListener;
    }

    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(super.getMethod());
            super.getMethod().invoke(super.getTarget(), progressListener);
        } catch (InvocationTargetException ex) {
            ReflectionUtils.rethrowRuntimeException(ex.getTargetException());
        } catch (IllegalAccessException ex) {
            throw new UndeclaredThrowableException(ex);
        }
    }

}

3.3.5 Task类的实现

public class Task {
    private long id;
    private String triggerName;
    private String cron;
    private long fixedDelay;
    private long fixedRate;
    private State state;
    private Date nextExecute;
    private Date lastExecute;
    private int progress;
    private String taskName;
    private long initialDelay;
    private String zone;
    private String initialDelayString;
    private String fixedRateString;
    private String fixedDelayString;


    @JsonIgnore
    private ScheduledTask scheduledTask;
    @JsonIgnore
    private Object bean;
    @JsonIgnore
    private Method method;

    //consturctors

    //setters and getters


    //除了构造方法外,应该提供三个方便的工具方法
    /**
    通过一个task对象把值复制到当前对象中
    */
    public void copyValueFromTask(Task task) {
        this.fixedRateString = task.getFixedRateString();
        this.fixedRate = task.getFixedRate();
        this.fixedDelayString = task.getFixedDelayString();
        this.fixedDelay = task.getFixedDelay();
        this.taskName = task.getTaskName();
        this.zone = task.getZone();
        this.initialDelay = task.getInitialDelay();
        this.initialDelayString = task.getInitialDelayString();
        this.cron = task.getCron();
        this.id = task.getId();
        this.lastExecute = task.getLastExecute();
        this.nextExecute = task.getNextExecute();
        this.progress = task.getProgress();
        this.state = task.getState();
        this.triggerName = task.getTriggerName();
    }

    /**
    * 从ProgressScheduled中获取调度信息,并保存
    */
    public void copyValueFromProgressScheduled(ProgressScheduled progressScheduled) {
        if (progressScheduled != null) {
            this.cron = progressScheduled.cron();
            this.initialDelay = progressScheduled.initialDelay();
            this.initialDelayString = progressScheduled.initialDelayString();
            this.fixedDelay = progressScheduled.fixedDelay();
            this.fixedDelayString = progressScheduled.fixedDelayString();
            this.fixedRate = progressScheduled.fixedRate();
            this.fixedRateString = progressScheduled.fixedRateString();
            this.zone = progressScheduled.zone();
            this.taskName = progressScheduled.name();
        }
    }

    /**
    * 调度后更新当前对象的属性。
    */
    public void addScheduledInfo(ScheduledTask scheduledTask, Object bean, Method method) {
        this.scheduledTask = scheduledTask;
        this.bean = bean;
        this.method = method;
    }
}  

3.3.6 动态调度:ProgressScheduledAnnotationBeanPostProcessor

  1. 继承ScheduledAnnotationBeanPostProcessor,同时由于我们需要维护一个TaskList,所以必须重写所有父类方法,那么这里继承的意义在于只要我们把原生调度任务传给父类就不会影响原生调度——使用@Scheduled标注的调度任务的执行。

       private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();
    //需要触发的taskUpdateListener
       private final List taskUpdateListeners = new ArrayList<>(8);
    //保存产生调度的方法的基本信息,可以在外面的类调用查看
       private final List taskList = new ArrayList<>(16);

  2. @Override标注的方法应调用super对应的方法来触发父类执行对任务的处理,例如:

       @Override
       public int getOrder() {
           super.getOrder();
           return LOWEST_PRECEDENCE;
       }

  3. postProcessAfterInitialization方法是对拥有@ProgressScheduled注解的方法进行读取的主要方法,它是BeanPostProcessor接口的一个方法,当每一个bean被提交之后将会调用。

       @Override
       public Object postProcessAfterInitialization(final Object bean, String beanName) {
           Class targetClass = AopUtils.getTargetClass(bean);
           if (!this.nonAnnotatedClasses.contains(targetClass)) {
               Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                       (MethodIntrospector.MetadataLookup>) method -> {
                           Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
                                   method, ProgressScheduled.class, ProgressSchedules.class);
                           return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
                       });
               if (annotatedMethods.isEmpty()) {
                   this.nonAnnotatedClasses.add(targetClass);
                   if (logger.isTraceEnabled()) {
                       logger.trace("No @ProgressScheduled annotations found on bean class: " + bean.getClass());
                   }
               } else {
                   // Non-empty set of methods
                   for (Map.Entry> entry : annotatedMethods.entrySet()) {
                       Method method = entry.getKey();
                       for (ProgressScheduled scheduled : entry.getValue()) {
                           Task task = new Task();
                           task.copyValueFromProgressScheduled(scheduled);
                           task.addScheduledInfo(null, bean, method);
                           processScheduled(task, method, bean);
                       }
                   }
                   if (logger.isDebugEnabled()) {
                       logger.debug(annotatedMethods.size() + " @ProgressScheduled methods processed on bean '" + beanName +
                               "': " + annotatedMethods);
                   }
               }
           }
           super.postProcessAfterInitialization(bean, beanName);
           return bean;
       }

  4. 重写processScheduled方法,将原使用Scheduled对象传递调度信息的方式改为Task对象传递调度信息,这里以cron表达式的调度方式为例,通过Task对象传递的信息创建CronTask并为每一个调度任务创建一个Runnable对象,实现为每个调度精准提供进度。

    // Check cron expression
               String cron = scheduled.getCron();
               if (StringUtils.hasText(cron)) {
                   Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
                   processedSchedule = true;
                   String zone = scheduled.getZone();
                   if (this.embeddedValueResolver != null) {
                       cron = this.embeddedValueResolver.resolveStringValue(cron);
                       zone = this.embeddedValueResolver.resolveStringValue(zone);
                   }
                   TimeZone timeZone;
                   if (StringUtils.hasText(zone)) {
                       timeZone = StringUtils.parseTimeZoneString(zone);
                   } else {
                       timeZone = TimeZone.getDefault();
                   }
                   Task task;
                   if (isScheduled) {
                       task = scheduled;
                   } else {
                       task = new Task(ATOMIC_INTEGER.getAndIncrement(),
                               method.toGenericString(),
                               cron,
                               Task.State.WAITING_NEXT,
                               null,
                               scheduled.getTaskName());
                   }
                   runnable = new ProgressScheduledMethodRunnable(bean,
                           invokableMethod,
                           p -> this.taskList.stream().
                                   filter(t -> task.getId() == t.getId())
                                   .forEach(t -> setTask(t, p, new CronTrigger(t.getCron()).nextExecutionTime(new SimpleTriggerContext()), false))
                   );
                   CronTask cronTask = new CronTask(runnable, new CronTrigger(cron, timeZone));
                   ScheduledTask scheduledTask = this.registrar.scheduleCronTask(cronTask);
                   task.addScheduledInfo(scheduledTask, bean, method);
                   if (isScheduled)
                       scheduled.addScheduledInfo(scheduledTask, bean, method);
                   else {
                       this.taskList.add(task);
                   }
               }

  5. 任务销毁,在应用程序退出,或其他任务需要销毁的情况下,spring会自动调用postProcessBeforeDestruction方法,但是调用这个方法有一个前提,就是在销毁之前调用的requiresDestruction方法返回true

      @Override
       public void postProcessBeforeDestruction(Object bean, String beanName) {
           synchronized (taskList) {
             //取消当前正在执行的任务,如果任务处于sleep状态,则中断它
               taskList
                       .stream()
                       .filter(task -> task.getBean().equals(bean) && task.getScheduledTask() != null)
                       .forEach(task -> task.getScheduledTask().cancel());
           }
           super.postProcessBeforeDestruction(bean, beanName);
       }
    
       @Override
       public boolean requiresDestruction(Object bean) {
    
           boolean rtn;
           rtn = super.requiresDestruction(bean);//当父类返回结果是true时同样需要处理
           synchronized (taskList) {
               rtn |= taskList
                       .stream()
                       .filter(task -> task.getBean().equals(bean))
                       .count() > 0;
           }
           return rtn;
       }

  6. 执行器关闭

    当外部调用distory方法时,意味着这个调度器不再被使用,因此,需要释放正在执行的任务:

       @Override
       public void destroy() {
           synchronized (this.taskList) {
               List removeTask =
                       taskList.stream()
                               .filter(task -> task.getScheduledTask() != null).collect(Collectors.toList());
               removeTask.forEach(task -> {
                   task.getScheduledTask().cancel();
                   taskList.remove(task);
               });
           }
           this.registrar.destroy();
    
           super.destroy();
       }

  7. 任务有变动需要通知TaskUpdateListener接口的实现类:

       private void notifyTaskListener(Task task) {
           taskUpdateListeners.forEach((listener) -> listener.onUpdate(task));
       }
    
       /**
        * 通知外部哪个任务发生了变化,进度当前是多少,下次实行时间是多少
        * @param task              who
        * @param progress          what
        * @param nextExecutionTime what
        * @param isRate            通过这个标识获取下次执行的时间
        */
       private void setTask(Task task, int progress, Date nextExecutionTime, boolean isRate) {
           task.setProgress(progress);
           if (progress == 0) {
               task.setState(Task.State.RUN);
               if (isRate)
                   task.setNextExecute(nextExecutionTime);
               else
                   task.setNextExecute(null);
           } else if (progress == 100) {
               task.setState(Task.State.WAITING_NEXT);
               if (!isRate)
                   task.setNextExecute(nextExecutionTime);
               task.setLastExecute(new Date());
           }
           notifyTaskListener(task);
       }

  8. 任务动态修改

    由于前面在生成任务的时候我们采用了一个自增长的ID,所以这个地方依然可以使用这个id来给任务做唯一标识:

    1. 当一个任务被post到后台时我首先判断这个任务的id是否存在。
    2. 如果提交的id我并不认识,那么我认为操作者的意图是要新增加一个任务,反之亦然。
    3. 得益于我们对processScheduled方法的重写,只需要传入一个Task对象调度即可进行。
       /**
        * 根据task对象进行调度
        *
        * @param task 修改的task对象
        */
       public void setTask(Task task) {
    
           if (taskList.stream()
                   .filter(t -> t.getId() == task.getId() && t.getTriggerName().equals(task.getTriggerName()))
                   .count() <= 0) {
               //在当前维护的taskList中没有找到传入的这个id——根据传入的TriggerName创建一个新的任务,并调度起来
               List rtn = taskList.stream()
                       .filter(t -> t.getTriggerName().equals(task.getTriggerName()))
                       .limit(1).collect(Collectors.toList());
               //.forEach(t -> this.processScheduled(task, t.getMethod(), t.getBean()));
               if (rtn != null) {
                   rtn.forEach(t -> this.processScheduled(task, t.getMethod(), t.getBean()));
               }
           } else {
               taskList.stream()//拿到我们维护的taskList的流
                       //添加过滤,只关注后台维护的list中和传入task对象id相等并且触发器名相同的对象
                       .filter(t -> t.getId() == task.getId() && t.getTriggerName().equals(task.getTriggerName()))
                       //得到的结果数最多为1,我们只关注它有没有结果,以及其中的一个结果,目的就是拿到这个任务的method对象
                       .limit(1)
                       .forEach(t -> {
                           //从传入的task中获取数据,并保存到list中匹配的task对象,这样做的好处就是不用频繁操作列表
                           t.copyValueFromTask(task);
                           //拿到曾经调度过这个任务的Future对象
                           ScheduledTask scheduledTask = t.getScheduledTask();
                           Object bean = t.getBean();
                           Method method = t.getMethod();
                           if (scheduledTask != null && bean != null && method != null) {
                               //取消之前调度的任务
                               scheduledTask.cancel();
                               //当传入task对象的状态不是stop时开启下一次调度,反之则说明操作者想要停止当前任务
                               if (!t.getState().equals(Task.State.STOP))
                                   this.processScheduled(t, method, bean);
                           }
                           notifyTaskListener(t);
                       });
           }
       }

  9. 注意事项

    在使用当前调度任务的每一个TaskUpdateListener的实现类中,都会收到任务发生变动时的task对象,而在task对象中包含了一个当前任务的Future对象scheduledTask,目前在监听器的实现类中可以通过getScheduledTask方法获取到这个对象,这会导致在监听器中可以取消当前任务,因此,在Task类中,应该将getScheduledTask方法的权限变更为package-private,只允许同胞兄弟访问。

项目地址

你可能感兴趣的:(java,java多线程)