分布式任务调度框架有:cronsun、Elastic-job、saturn、lts、TBSchedule、xxl-job 等。
(1)Timer.schedule(TimerTask task,Date time)安排在制定的时间执行指定的任务。
(2)Timer.schedule(TimerTask task,Date firstTime ,long period)安排指定的任务在指定的时间开始进行重复的固定延迟执行.
(3)Timer.schedule(TimerTask task,long delay)安排在指定延迟后执行指定的任务.
(4)Timer.schedule(TimerTask task,long delay,long period)安排指定的任务从指定的延迟后开始进行重复的固定延迟执行.
(5)Timer.scheduleAtFixedRate(TimerTask task,Date firstTime,long period)安排指定的任务在指定的时间开始进行重复的固定速率执行.
(6)Timer.scheduleAtFixedRate(TimerTask task,long delay,long period)安排指定的任务在指定的延迟后开始进行重复的固定速率执行.
public class TestTimer{
public static void main(String[] args){
TimerTask timerTask = new TimerTask(){
@Override
public void run(){
System.out.println("tesk run:" + new Date());
}
};
Timer timer = new Timer();
//安排指定的任务在指定的时间开始进行重复的而固定延迟执行,每3庙执行一次
timer.schedule(timeTask,10,3000);
}
}
public class TestScheduleExecutorService{
public static void main(String[] args){
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
/**
参数:任务体、首次执行的延时时间、任务执行间隔、间隔时间单位
*/
service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()),
0, 3, TimeUnit.SECONDS);
}
}
Cron表达式:若干数字、空格、符号按照一定的规则组成一组字符串来表达时间信息。在线corn表达式生成器:https://www.pppet.net/
标准格式:A B C D E F G
A表示秒;B表示分;C表示小时;D表示日;E表示月;F表示星期;G表示年
spring3.0以后自带的task
注解方式:
<task:annotation-driven/>
@Slf4j
@Component
public class ScheduledService{
@Scheduled(cron = "0/5 * * * * *")
public void scheduled(){
log.info("====>使用cron {}",System.currentTimeMillis());
}
@Scheduled(fixedRate = 5000) //每5秒运行一次
public void scheduled1(){
log.info("=====>>>>>使用fixedRate{}", System.currentTimeMillis());
}
@Scheduled(fixedDelay = 5000)
public void scheduled2() {
log.info("=====>>>>>fixedDelay{}",System.currentTimeMillis());
}
}
配置方式:
<task:scheduled-tasks>
<task:scheduled ref="job1" method="run" fixed-rate="5000"/>
task:scheduled-tasks>
每5秒执行一次
public class job1 {
public void run(){
System.out.println("job1....."+new Date());
}
}
整合SpringBoot
①、启动类添加注解,开启定时任务@EnableScheduling
②、方法上添加注解表达式
@Slf4j
@Component //项目启动的时候能够被初始化出来
public class WxPayTask{
/**
SpringTask引入的linux的cron表达式
秒 分 时 日 月 周
日和周是互斥的,指定其中之一,另一个为?
* 每秒都执行
1-3 从第0秒开始,每个3秒执行一次
0/3 从第0秒开始,每隔3秒执行1次
1,2,3 在指定的第1,2,3秒执行
*/
@Scheduled(cron = "* * * * * ?")//每月每天没时每分每秒执行
public void task1(){
log.info("task1被执行");
}
//从第0秒开始,每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单
@Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm(){
log.info("orderConfirm 被执行……");
//查询超过5分钟的订单
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOderByDuration(5);
for(OrderInfo orderInfo:orderInfoList){
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单==={}",orderNo);
//核实订单状态,调用微信支付
}
}
@Override
public List<OrderInfo> getNoPayOrderByDuration(int minutes){
Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));//
//构造查询条件(超过5分钟+未支付)
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
queryWrapper.le("create_time",instant);
List<OrderInfo> orderInfoList = baseMapper.selectList(queryWrapper);
return orderInfoList;
}
//根据订单号查询微信支付查单接口
//如果订单已支付,则更新商户端订单状态,如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
@Override
public void checkOrderStatus(String orderNo){
log.warn("根据订单号核实订单状态===>",orderNo);
//调用微信支付接口
String result = this.queryOrder(orderNo);
//利用工具将json转换map
Gson gson = new Gson();
Map resultMap = gson.fromJson(result,HashMap.class);
//获取订单状态
Object tradeState = resultMap.get("trade+state");
//判断订单状态
if(WxTradeState.SUCCESS.getType().equals(tradeState)){
log.warn("核实订单已支付===>{}",orderNo);
//如果确认订单已支付则更新本地订单状态
orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);
//记录支付日志
paymentInfoService.createPaymentInfo(result);
}
if(WxTrade.NOPAY.getType().equals(tradeState)){
log.warn("核实订单未支付===>",orderNo);
this.closeOrder(orderNo);
}
}
定时任务+RabbitMQ
@Component
@EnableScheduling
public class ScheduleTask{
@Autowired
private RabbitService rabbitService;
//每天8点执行
@Schedule(cron = "0 0 8 * * ?")
public void taskPatient(){
//通过mq发送消息
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK,MqConst.ROUTING_TASK_8,"");
}
}
//对消息的监听,进行接收消息
@Componet
public class OrderReceiver{
@Autowired
private OrderService orderService;
@RabbitListener(bindings = @QueueBinding(
value=@Queue(value=MqConst.QUEUE_TASK_8,durable="true"),
exchange=@Exchange(value=MqConst.EXCHANE_DIRECT_TASK),
key={MqConst.ROUTING_TASK_8}
))
public void patientTips(Message message,Channel channel) throws IOException{
orderService.patientTips();
}
}
//业务接口以及实现
@Override
public void patientTips(){
QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
wrapper.eq("reserve_data",new DateTime().toString("yyyy-MM-dd"));
wrapper.ne("order_status",OrderStatusEnum.CANCLE.getStatus());
List<OrderInfo> orderInfoList = baseMapper.selectList(wrapper);
for(OrderInfo orderInfo:orderInfoList){
MsmVo msmVo = new MsmVo();
msmVo.setPhone(orderInfo.getPatientPhone());
String reserveData = new DateTime(orderInfo.getReserveDate()).toString();
Map<String,Object> param = new HashMap<String,Object>(){
{
put("title",orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
put("reserveData",reserveDate);
put("name",orderInfo.getPatientName());
}
}
rebbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM,MqConst.ROUTING_MSM_ITEM,msmVo);
}
}
基于注解设置多线程
@Component
@EnableScheduling //开启定时任务
@EnableAsync //开启多线程
public class MultithreadScheduleTask{
@Async
@Scheduled(fixedDDelay = 5000) //间隔5秒
public void first(){
System.out.println("第一个定时任务开始:" + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}
@Async
@Scheduled(fixedDelay = 5000)
public void second() {
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
}
}
五种多线程方法提高效率
线程类(Thread),直接控制线程的创建和管理
public class CustomTask implements Runnable{
private final String name;
private final int count;
CustomTask(String name,int count){
this.name = name;
this.count = count;
}
@Override
public void run(){
//每隔50毫秒从0数到count - 1
for(int i = 0;i < count;i++){
System.out.println(name + "" + i + "" + Thread.currentThread().getName());
try{
Thread.sleep(50);
}catch(InterruptedException e){
throw new RuntimeException(e);
}
}
}
}
//创建三个实例
Thread a = new Thread(new CustomTask("a",5));
Thread b = new Thread(new CustomTask("b",10));//b 预计计数的次数是其他实例的两倍
Thread c = new Thread(new CustomTask("c",5));
// 首先启动 a 和 b。
a.start();
b.start();
// a 完成后开始 c。
a.join();
c.start();
a 和 b 同时开始运行,轮流输出。a 完成后,c 开始执行。此外,它们全部在不同的线程中运行。通过手动创建 Thread 实例,您可以完全控制它们。
并行流(Parallel Streams),需要对大型集合中的所有元素应用相同、重复且独立的任务时,并行流非常有效
例如,图像调整大小是一个需要按顺序运行的繁重任务;当您有多个图像需要调整大小时,如果按顺序执行,将需要很长时间才能完成。在这种情况下,您可以使用并行流并行调整它们的大小
private static List<BurreredImage> resizeAll(List<BufferedImage> sourceImages,int width,int height){
return sourceImages
.parallelStream()
.map(source -> resize(source,width,height))
.toList();
}
ExecutorService,当实现不需要精确的线程控制时,可以考虑使用 ExecutorService。
ExecutorService 提供了更高层次的线程管理抽象,包括线程池、任务调度和资源管理
大量的异步任务堆积在一起,但是同时运行所有任务——每个任务占用一个线程——似乎太多了。线程池可以通过限制最大线程数来帮助
使用 Executors.newFixedThreadPool() 实例化 ExecutorService 来使用 3 个线程运行 10 个任务。每个任务只打印一行。请注意,我们在之前的部分中重用了之前定义的 CustomTask
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i = 0;i < 10;i++){
executorService.submit(new CustomTask(String.valueOf(i),1));
}
executorService.shutdown();
10 个任务在 3 个线程中运行。通过限制特定任务使用的线程数,您可以根据优先级分配线程数:对于重要且频繁的任务使用更多线程,对于琐碎或偶尔的任务使用较少线程。ExecutorService 具有高效和简洁的特点,是大多数多线程场景的首选选项。
如果您需要更多的控制和灵活性,请查看 ThreadPoolExecutor,它是 Executors.newFixedThreadPool() 返回的 ExecutorService 的实际实现。您可以直接创建其实例或将返回的 ExecutorService 实例转换为 ThreadPoolExecutor 实例以获得更多控制权。
ForkJoinPool,是另一种线程池。其中一个任务是图像调整大小。图像调整大小是分而治之问题的一个很好的例子。使用ForkJoinPool,您可以将图像分成两个或四个较小的图像,并同时调整它们的大小。以下是ImageResizeAction的示例,它将图像调整为给定的大小。
public class ImageResizeAction extends RecursizeAction{
private static final int THRESHOLD = 100;
private final BufferedImage sourceImage;
private final BufferedImage targetImage;
private final int startRow;
private final int endRow;
private final int targetWidth;
private final int targetHeight;
public ImageResizeAction(BufferedImage sourceImage,
BufferedImage targetImage,
int startRow, int endRow,
int targetWidth, int targetHeight) {
this.sourceImage = sourceImage;
this.targetImage = targetImage;
this.startRow = startRow;
this.endRow = endRow;
this.targetWidth = targetWidth;
this.targetHeight = targetHeight;
}
@Override
protected void compute(){
if(endRow - startRow <= THRESHOLD){
resizeImage();
}else{
int midRow = startRow + (endRow - startRow) / 2;
invokeAll(
new ImageResizeAction(sourceImage, targetImage,
startRow, midRow, targetWidth, targetHeight),
new ImageResizeAction(sourceImage, targetImage,
midRow, endRow, targetWidth, targetHeight)
);
}
}
private void resizeImage() {
int sourceWidth = sourceImage.getWidth();
double xScale = (double) targetWidth / sourceWidth;
double yScale = (double) targetHeight / sourceImage.getHeight();
for (int y = startRow; y < endRow; y++) {
for (int x = 0; x < sourceWidth; x++) {
int targetX = (int) (x * xScale);
int targetY = (int) (y * yScale);
int rgb = sourceImage.getRGB(x, y);
targetImage.setRGB(targetX, targetY, rgb);
}
}
}
}
请注意,ImageResizeAction继承了RecursiveAction。RecursiveAction用于定义递归的调整大小操作。在此示例中,图像被分成两半并并行调整大小。
您可以使用以下代码运行ImageResizeAction:
public static void main(String[] args) throws IOException {
String sourceImagePath = "source_image.jpg";
String targetImagePath = "target_image.png";
int targetWidth = 300;
int targetHeight = 100;
BufferedImage sourceImage = ImageIO.read(new File(sourceImagePath));
BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight,
BufferedImage.TYPE_INT_RGB);
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.invoke(new ImageResizeAction(sourceImage, targetImage,
0, sourceImage.getHeight(), targetWidth, targetHeight));
ImageIO.write(targetImage, "png", new File(targetImagePath));
System.out.println("图像调整大小成功!");
}
借助ForkJoinPool的帮助,您现在能够更高效地调整图像的大小,具有更好的可伸缩性,并最大程度地利用资源
CompletableFuture,能够链式地连接异步操作,使您能够构建复杂的异步管道。
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
return "Hyuni Kim";
}).thenApply((data) -> {
System.out.println(Thread.currentThread().getName());
return "我的名字是" + data;
}).thenAccept((data) -> {
System.out.println(Thread.currentThread().getName());
System.out.println("结果:" + data);
});
future.join();
}
链式操作。通过CompletableFuture.supplyAsync(),首先创建并运行一个返回字符串结果的CompletableFuture。thenApply()接受前一个任务的结果,并执行其他操作,本例中是添加一个字符串。最后,thenAccept()打印生成的数据。结果如下所示:
有3个任务没有在主线程中运行,这表明它们与主逻辑并行运行。当您有具有结果并需要链接的任务时,CompletableFuture将是一个很好的选择。
基于时间间隔的定时任务
基于Cron表达式的定时任务
Job具体的工作内容/Trigger触发器,指定运行参数(运行次数、开始时间、时长)/Scheduler将job和Trigger关联起来
多触发器的定时任务、Job中注入Bean、Quartz持久化
一个JobDetail(Job的实现类)可以绑定多个Trigger,但一个Trigger只能绑定一个JobDetail
每个JobDetail和Trigger通过group和name来标识唯一性;
一个Scheduler可以调度多组JobDetail和Trigger。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-quartzartifactId>
dependency>
@Configuration
public class QuartzConfig{
@Bean
public JobDetail teatQuaritzDetail(){
return JobBuilder.newJob(MyAuartz.class).withIdentity("myQuartz").storeDurably().build();
}
@Bean
public Trigger testQuartzTrigger(){
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10) //设置时间周期单位
.repeatForever();
return TriggerBuilder.newTrigger().forJob(testQuartzDetail())
.withIdentity("testQuartz")
.withSchedule(scheduleBuilder)
.build();
}
}
配置的方式:
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<version>2.3.2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>5.3.2version>
dependency>
<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="500" />
bean>
<bean id="quartzJob" class="com.wanmait.mavendemo.job.QuartzJob" />
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="quartzJob" />
<property name="targetMethod" value="print" />
bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean" >
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0/5 * * * * ?" />
bean>
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail">property>
<property name="startDelay" value="0" />
<property name="repeatInterval" value="2000" />
bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
list>
property>
<property name="taskExecutor" ref="executor" />
bean>
public class QuartzJob{
public void print(){
System.out.println("quartzjob.........."+new Date());
}
}
SpringBoot定时任务:
在启动类上加@EnableScheduling开启定时任务
写一个任务类添加@Component注解被spring管理,在方法上添加@Scheduled(fixedRate = 2000)或者 @Scheduled(cron = “0-5 * * ? * *”)
案例二:
导入依赖spring-boot-starter-quartz
新建Job,实现定时执行的任务
public class SimpleJob implements Job{
@Override
public void execute(JobExecutionContext jobExecutionContext){
//创建一个事件
System.out.println(Thread.currentThread().getName() + "--"
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
}
}
创建Scheduler和Trigger,执行定时任务
public class SimpleQuartzTest{
/**
基于时间间隔的定时任务
*/
@Test
public void simpleTest() throws SchedulerException, InterruptedException{
//1、创建Scheduler调度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//2.创建JobDetail实例,并于SimpleJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1","group1")
.build();
//3.构建Trigger(触发器),定义执行频率和时长
Trigger trigger = TriggerBuilder.newTrigger()
//指定group和name,这是以为身份标识
.withIdentity("trigger-1","trigger-group")
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)//每个2s执行一次
.repeatForever())//永久执行
.build();
//4.将job和Trigger交给Scheduler调度
scheduler.scheduleJob(jobDetail,trigger);
//5.启动Scheduler
scheduler.start();
//休眠,决定调度器运行时间,这里设置30s
TimeUnit.SECONDS.sleep(30);
//关闭scheduler
scheduler.shutdown();
}
}
启动测试方法后,控制台观察现象即可。注意到这么一句日志:Using thread pool ‘org.quartz.simpl.SimpleThreadPool’ - with 10 threads.,这说明Scheduler确实是内置了10个线程的线程池,通过打印线程名也印证了这一点。
通过TimeUnit.SECONDS.sleep(30);设置休眠,是因为定时任务是交由线程池异步执行的,而测试方法运行结束,主线程随之结束导致定时任务也不再执行了,所以需要设置休眠hold住主线程。在真实项目中,项目的进程是一直存活的,因此不需要设置休眠时间。
@Configuration
public class ScheduledConfig implements SchedulingConfigurer{
@Autowired
private ApplicationContext context;
@Override
public void confugureTasks(ScheduledTaskRegistrar taskRegistrar){
for(SpringScheuledCron springScheduledCron:cronRepository.findAll()){
Class<?> clazz;
Object task;
try{
clazz = Class.forName(springScheduledCron.getCronKey());
task = context.getBean(clazz);
}catch(ClassNotFoundException e){
throw new IllegalArgumentException("spring_scheduled_cron表数据" + springScheduledCron.getCronKey() + "有误", e);
}catch(BeansException e){
throw new IllegalArgumentException(springScheduledCron.getCronKey() + "未纳入到spring管理", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
//可以通过改变书库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable)task),
triggerContext -> {
//可以使用持久层,比如Mybatis来实现,从数据库中获取
String cronExpression = "0/10 * * * * ?";
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
}
}
@Bean
public Executor taskExecutor(){
return Executors.newScheduledThreadPool(10);
}
}
基于Cron表达式的定时任务
public class SimpleQuartzTest {
/*
* 基于cron表达式的定时任务
*/
@Test
public void cronTest() throws SchedulerException, InterruptedException {
// 1、创建Scheduler(调度器)
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 2、创建JobDetail实例,并与SimpleJob类绑定
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job-1", "job-group").build();
// 3、构建Trigger(触发器),定义执行频率和时长
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("trigger-1", "trigger-group")
.startNow() //立即生效
.withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 *"))
.build();
//4、执行
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
// 休眠,决定调度器运行时间,这里设置30s
TimeUnit.SECONDS.sleep(30);
// 关闭Scheduler
scheduler.shutdown();
}
}