public class TestScheduled { |
|
/** |
|
* 1、使用Spring自带的TaskScheduler注册任务 |
|
* 2、注册后返回:ScheduledFuture,用于取消定时任务 |
|
*/ |
|
@Resource |
|
private TaskScheduler taskScheduler; |
|
public void registrarTask() { |
|
//具体的任务Runnable(一般使用类实现Runnable接口) |
|
Runnable taskRunnable = new Runnable() { |
|
@Override |
|
public void run() { |
|
} |
|
}; |
|
//cron表达式触发器 |
|
CronTrigger trigger = new CronTrigger("0/5 * * * * ?"); |
|
//开启定时任务的真正方法 |
|
ScheduledFuture> future = this.taskScheduler.schedule(taskRunnable, trigger); |
|
//取消定时任务 |
|
future.cancel(true); |
|
} |
|
} |
package com.cc.ssd.config; |
|
import org.springframework.context.annotation.Bean; |
|
import org.springframework.context.annotation.Configuration; |
|
import org.springframework.scheduling.TaskScheduler; |
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; |
|
/** TaskScheduler任务调度器配置类 |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
@Configuration |
|
public class CronTaskConfig { |
|
/** |
|
* 任务调度器自定义配置 |
|
*/ |
|
@Bean(name = "taskScheduler") |
|
public TaskScheduler taskScheduler() { |
|
// 任务调度线程池 |
|
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); |
|
// 定时任务执行线程池核心线程数:可同时执行4个任务 |
|
taskScheduler.setPoolSize(4); |
|
taskScheduler.setRemoveOnCancelPolicy(true); |
|
// 线程名称前缀 |
|
taskScheduler.setThreadNamePrefix("Cs-ThreadPool-"); |
|
return taskScheduler; |
|
} |
|
} |
package com.cc.ssd.registrar; |
|
import com.cc.ssd.task.CronTaskFuture; |
|
import com.cc.ssd.task.CronTaskRunnable; |
|
import org.slf4j.Logger; |
|
import org.slf4j.LoggerFactory; |
|
import org.springframework.beans.BeanUtils; |
|
import org.springframework.beans.factory.DisposableBean; |
|
import org.springframework.scheduling.TaskScheduler; |
|
import org.springframework.scheduling.config.CronTask; |
|
import org.springframework.scheduling.support.CronExpression; |
|
import org.springframework.stereotype.Component; |
|
import org.springframework.util.Assert; |
|
import javax.annotation.Resource; |
|
import java.time.LocalDateTime; |
|
import java.time.format.DateTimeFormatter; |
|
import java.util.*; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
import java.util.stream.Collectors; |
|
/** 注册定时任务:缓存定时任务、注册定时任务到调度中心 |
|
* @author CC |
|
**/ |
|
@Component |
|
public class CronTaskRegistrar implements DisposableBean { |
|
private static final Logger log = LoggerFactory.getLogger(CronTaskRegistrar.class); |
|
/** |
|
* 缓存任务 |
|
* key:具体的任务 |
|
* value:注册定时任务后返回的ScheduledFuture |
|
*/ |
|
private final Map |
|
/** |
|
* 使用自定义的任务调度配置 |
|
*/ |
|
@Resource(name = "taskScheduler") |
|
private TaskScheduler taskScheduler; |
|
/** 获取任务调度配置 |
|
* @return 任务调度配置 |
|
*/ |
|
public TaskScheduler getTaskScheduler() { |
|
return this.taskScheduler; |
|
} |
|
/** 新增定时任务1 |
|
* 存在任务:删除此任务,重新新增这个任务 |
|
* @param taskRunnable 执行的具体任务定义:taskRunnable 实现Runnable |
|
* @param cronExpression cron表达式 |
|
*/ |
|
public void addCronTask(Runnable taskRunnable, String cronExpression) { |
|
//验证cron表达式是否正确 |
|
boolean validExpression = CronExpression.isValidExpression(cronExpression); |
|
if (!validExpression) { |
|
throw new RuntimeException("cron表达式验证失败!"); |
|
} |
|
//获取下次执行时间 |
|
CronExpression parse = CronExpression.parse(cronExpression); |
|
LocalDateTime next = parse.next(LocalDateTime.now()); |
|
if (Objects.nonNull(next)) { |
|
//定时任务下次执行的时间 |
|
String format = next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); |
|
log.info("定时任务下次执行的时间:{}", format); |
|
} |
|
//封装成 CronTask(cron任务) |
|
CronTask cronTask = new CronTask(taskRunnable, cronExpression); |
|
this.addCronTask(cronTask); |
|
} |
|
/** 新增定时任务2 |
|
* @param cronTask : |
|
* |
|
* |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
private void addCronTask(CronTask cronTask) { |
|
if (Objects.nonNull(cronTask)) { |
|
//1有这个任务,先删除这个任务。再新增 |
|
Runnable task = cronTask.getRunnable(); |
|
String taskId = null; |
|
if (task instanceof CronTaskRunnable) { |
|
taskId = ((CronTaskRunnable) task).getTaskId(); |
|
} |
|
//通过任务id获取缓存的任务,如果包含则删除,然后新增任务 |
|
Runnable taskCache = this.getTaskByTaskId(taskId); |
|
if (Objects.nonNull(taskCache) && this.scheduledTasks.containsKey(taskCache)) { |
|
this.removeCronTaskByTaskId(taskId); |
|
} |
|
//2注册定时任务到调度中心 |
|
CronTaskFuture scheduledFutureTask = this.scheduleCronTask(cronTask); |
|
//3缓存定时任务 |
|
this.scheduledTasks.put(task, scheduledFutureTask); |
|
//todo cc 4可以将任务保存到数据库中……重新启动程序然后加载数据库中的任务到缓存中…… |
|
} |
|
} |
|
/** 注册 ScheduledTask 定时任务 |
|
* @param cronTask cronTask |
|
* @return 注册定时任务后返回的 ScheduledFutureTask |
|
*/ |
|
private CronTaskFuture scheduleCronTask(CronTask cronTask) { |
|
//注册定时任务后记录的Future |
|
CronTaskFuture scheduledTask = new CronTaskFuture(); |
|
//开启定时任务的真正方法 |
|
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()); |
|
// scheduledTask.setThreadLocal(this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger())); |
|
return scheduledTask; |
|
} |
|
/** 获取任务列表 |
|
* @return |
|
*/ |
|
public List |
|
List |
|
Set |
|
keySet.forEach(key -> { |
|
CronTaskRunnable task = new CronTaskRunnable(); |
|
if (key instanceof CronTaskRunnable) { |
|
CronTaskRunnable taskParent = (CronTaskRunnable) key; |
|
BeanUtils.copyProperties(taskParent, task); |
|
} |
|
tasks.add(task); |
|
}); |
|
return tasks.stream() |
|
.sorted(Comparator.comparing(CronTaskRunnable::getTaskId)) |
|
.collect(Collectors.toList()); |
|
} |
|
/** 根据任务id删除单个定时任务 |
|
* @param taskId 任务id |
|
*/ |
|
public void removeCronTaskByTaskId(String taskId) { |
|
//通过任务id获取任务 |
|
Runnable task = this.getTaskByTaskId(taskId); |
|
//需要通过任务id获取任务,然后再移除 |
|
CronTaskFuture cronTaskFuture = this.scheduledTasks.remove(task); |
|
if (Objects.nonNull(cronTaskFuture)) { |
|
cronTaskFuture.cancel(); |
|
} |
|
} |
|
/** 通过任务id获取任务。未查询到返回null |
|
* @param taskId 任务id |
|
* @return java.lang.Runnable |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
private Runnable getTaskByTaskId(String taskId) { |
|
Assert.notNull(taskId, "任务id不能为空!"); |
|
Set |
|
//根据任务id获取该任务缓存 |
|
Map.Entry |
|
Runnable key = rf.getKey(); |
|
String taskId1 = null; |
|
if (key instanceof CronTaskRunnable) { |
|
taskId1 = ((CronTaskRunnable) key).getTaskId(); |
|
} |
|
return taskId.equals(taskId1); |
|
}).findAny().orElse(null); |
|
if (Objects.nonNull(rcf)) { |
|
return rcf.getKey(); |
|
} |
|
return null; |
|
} |
|
/** 删除所有的定时任务 |
|
* DisposableBean是Spring框架中的一个接口,它定义了一个destroy()方法, |
|
* 用于在Bean销毁时执行清理工作。 |
|
* 当一个Bean实现了DisposableBean接口时, |
|
* Spring容器会在该Bean销毁时自动调用destroy()方法, |
|
* 以便进行一些清理工作,例如释放资源等。 |
|
* 如果您的Bean需要在销毁时执行一些清理工作, |
|
* 那么实现DisposableBean接口是一个很好的选择。 |
|
*/ |
|
@Override |
|
public void destroy() { |
|
//关闭所有定时任务 |
|
for (CronTaskFuture task : this.scheduledTasks.values()) { |
|
task.cancel(); |
|
} |
|
//清空缓存 |
|
this.scheduledTasks.clear(); |
|
log.info("取消所有定时任务!"); |
|
//todo cc 修改或删除数据库的任务 |
|
} |
|
} |
|
package com.cc.ssd.task; |
|
import java.util.Objects; |
|
import java.util.concurrent.ScheduledFuture; |
|
/** CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。 |
|
* ——最后ps:也可以不要这个记录类,直接缓存ScheduledFuture对象。 |
|
* 用来记录单独的Future、定时任务注册任务后产生的 |
|
* @author CC |
|
**/ |
|
public final class CronTaskFuture { |
|
/** 每个线程一个副本 |
|
* 经过测试这里不能使用ThreadLocal |
|
*/ |
|
// private static final ThreadLocal |
|
/** 最后ps:由于ScheduledFuture是线程安全的。这里不用 volatile 或者 ThreadLocal |
|
* 注册任务后返回的:ScheduledFuture 用于记录并取消任务 |
|
* 这两个都可以不使用。直接给future赋值 |
|
* volatile:线程之间可见:volatile用于实现多线程之间的可见性和一致性,保证数据的正确性。 |
|
* ThreadLocal:用于实现线程封闭,保证线程安全 |
|
* 使用建议: |
|
* CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。 |
|
* ScheduledFuture对象是线程安全的,因此不需要使用volatile关键字来保证多线程同步。 |
|
* 如果需要在多线程中使用线程本地变量,可以使用ThreadLocal。 |
|
* 因此,建议在CronTaskFuture类中使用ScheduledFuture对象,而不是使用volatile或ThreadLocal。 |
|
* 另外,如果需要在Spring容器销毁时执行一些清理操作,可以实现DisposableBean接口,并在destroy()方法中进行清理操作。 |
|
*/ |
|
public ScheduledFuture> future; |
|
// public volatile ScheduledFuture> future; |
|
// public void setThreadLocal(ScheduledFuture> future){ |
|
// THREAD_LOCAL.set(future); |
|
// } |
|
/** |
|
* 取消当前定时任务 |
|
*/ |
|
public void cancel() { |
|
try { |
|
// ScheduledFuture> future = THREAD_LOCAL.get(); |
|
ScheduledFuture> future = this.future; |
|
if (Objects.nonNull(future)) { |
|
future.cancel(true); |
|
} |
|
} catch (Exception e) { |
|
throw new RuntimeException("销毁定时任务失败!"); |
|
} finally { |
|
// THREAD_LOCAL.remove(); |
|
} |
|
} |
|
} |
|
package com.cc.ssd.task; |
|
import lombok.Data; |
|
import org.slf4j.Logger; |
|
import org.slf4j.LoggerFactory; |
|
import org.springframework.stereotype.Component; |
|
/** 具体任务实现 |
|
* @author CC |
|
* @since 2023/4/21 0021 |
|
*/ |
|
@Data |
|
public class CronTaskRunnable implements Runnable { |
|
private static final Logger log = LoggerFactory.getLogger(CronTaskRunnable.class); |
|
/** |
|
* 任务id(必须唯一) |
|
*/ |
|
private String taskId; |
|
/** |
|
* 任务类型:自定义 |
|
*/ |
|
private Integer taskType; |
|
/** |
|
* 任务名字 |
|
*/ |
|
private String taskName; |
|
/** |
|
* 任务参数 |
|
*/ |
|
private Object[] params; |
|
public CronTaskRunnable() { |
|
} |
|
public CronTaskRunnable(String taskId, Integer taskType, String taskName, Object... params) { |
|
this.taskId = taskId; |
|
this.taskType = taskType; |
|
this.taskName = taskName; |
|
this.params = params; |
|
} |
|
/** 执行任务 |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
@Override |
|
public void run() { |
|
long start = System.currentTimeMillis(); |
|
//具体的任务。 |
|
log.info("\n\t {}号.定时任务开始执行 - taskId:{},taskName:{},taskType:{},params:{}", |
|
taskType, taskId, taskName, taskType, params); |
|
//任务处理的方式: |
|
//todo cc 1就在这里执行:模拟任务 |
|
//todo cc 2开启策略模式,根据任务类型 调度不同的任务 |
|
//todo cc 3使用反射:传来bean名字,方法名字,调用不同的任务 |
|
//todo cc 4开启队列,把要执行的任务放到队列中,然后执行 —— 使用场景:每个任务执行很耗时的情况下使用 |
|
try { |
|
Thread.sleep(1000); |
|
} catch (InterruptedException e) { |
|
throw new RuntimeException(e); |
|
} |
|
log.info("\n\t {}号.任务执行完成 - 耗时:{},taskId:{},taskType:{}", |
|
taskType, System.currentTimeMillis() - start, taskId, taskType); |
|
} |
|
} |
|
package com.cc.ssd.web.controller; |
|
import com.cc.ssd.registrar.CronTaskRegistrar; |
|
import com.cc.ssd.task.CronTaskRunnable; |
|
import org.springframework.web.bind.annotation.*; |
|
import javax.annotation.Resource; |
|
import java.util.List; |
|
import java.util.Map; |
|
/** |
|
* @author CC |
|
* @since 2023/4/21 0021 |
|
*/ |
|
@RestController |
|
@RequestMapping("/scheduled") |
|
public class TestScheduledController { |
|
@Resource |
|
private CronTaskRegistrar cronTaskRegistrar; |
|
/** 获取任务列表 |
|
* @return java.util.List |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
@GetMapping |
|
public List |
|
return cronTaskRegistrar.getScheduledTasks(); |
|
} |
|
/** 添加任务 |
|
* @param param param |
|
* @return java.lang.String |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
@PostMapping |
|
public String addCronTask(@RequestBody Map |
|
//自己拿任务参数的逻辑:可以把每个任务保存到数据库,重新启动任务的同时,加载这些任务到任务调度中心 |
|
String taskId = (String) param.get("taskId"); |
|
Integer taskType = (Integer) param.get("taskType"); |
|
String taskName = (String) param.get("taskName"); |
|
Object params = param.get("params"); |
|
//添加任务参数 |
|
CronTaskRunnable task = new CronTaskRunnable(taskId, taskType, taskName, params); |
|
//注册任务:cron表达式,可以从传入不一样的 |
|
cronTaskRegistrar.addCronTask(task, "0/5 * * * * ?"); |
|
return "ok"; |
|
} |
|
/** 根据任务id删除定时任务 |
|
* @param taskId 任务id |
|
* @return java.lang.String |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
@DeleteMapping |
|
public String removeCronTaskByTaskId(@RequestParam String taskId) { |
|
cronTaskRegistrar.removeCronTaskByTaskId(taskId); |
|
return "ok"; |
|
} |
|
/** 删除全部任务 |
|
* @return java.lang.String |
|
* @since 2023/4/21 0021 |
|
* @author CC |
|
**/ |
|
@DeleteMapping("/removeAll") |
|
public String removeCronTask() { |
|
cronTaskRegistrar.destroy(); |
|
return "ok"; |
|
} |
|
} |
|