Java 定时调度及 Spring Task 使用

18 篇文章 0 订阅

1. Timer

Timer需要和TimerTask配合使用,才能完成调度功能。Timer表示调度器,TimerTask表示调度器执行的任务。

实现的方式为单线程,因此从JDK1.3发布之后就一直存在一些问题,大致如下:

  1. 多个任务之间会相互影响
  2. 多个任务的执行是串行的,性能较低

1.1 一次性调度

延迟一秒后执行一次

public static void main (String[] args) {
    Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println(dateFormat.format(new Date()));
        }
    };
    timer.schedule(timerTask, 1000);
}

1.2 循环调度

scheduledExecutionTime() 返回最近的计划执行时间实际执行此任务。如果调用此方法在执行任务时,返回值是计划的正在进行的任务执行的执行时间。

schedule()
固定时间的调度方式,延迟一秒后,再每隔两秒执行一次

public static void main (String[] args) {
    Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("scheduledExecutionTime: " + dateFormat.format(scheduledExecutionTime()));
            System.out.println("Date: " + dateFormat.format(new Date()));
            System.out.println();
        }
    };
    timer.schedule(timerTask, 1000, 2000);
}

当执行时间超过间隔时间,任务的下一次执行时间是相对于上一次实际执行完成的时间点 ,因此执行时间会不断延后。

输出:

scheduledExecutionTime: 14:19:40
Date: 14:19:43

scheduledExecutionTime: 14:19:43
Date: 14:19:46

scheduledExecutionTime: 14:19:46
Date: 14:19:49

scheduleAtFixedRate()
固定频率的调度方式,延迟一秒,再每隔两秒执行一次

public static void main (String[] args) {
    Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("scheduledExecutionTime: " + dateFormat.format(scheduledExecutionTime()));
            System.out.println("Date: " + dateFormat.format(new Date()));
            System.out.println();
        }
    };
    timer.scheduleAtFixedRate(timerTask, 1000, 2000);
}

当执行时间超过间隔时间,任务的下一次执行时间是相对于上一次开始执行的时间点 ,因此执行时间不会延后。

输出:

scheduledExecutionTime: 14:20:49
Date: 14:20:52

cheduledExecutionTime: 14:20:51
Date: 14:20:55

scheduledExecutionTime: 14:20:53
Date: 14:20:58

2. ScheduledExecutorService

ScheduledExecutorService在设计之初就是为了解决Timer&TimerTask的这些问题。因为天生就是基于多线程机制,所以任务之间不会相互影响(只要线程数足够。当线程数不足时,有些任务会复用同一个线程)。

除此之外,因为其内部使用的延迟队列,本身就是基于等待/唤醒机制实现的,所以CPU并不会一直繁忙。同时,多线程带来的CPU资源复用也能极大地提升性能。

2.1 一次性调度

schedule()方法有很多重载,可以传Runnable或Callable接口。

Runnable无返回值,延迟一秒后执行

private void schedule() throws InterruptedException, ExecutionException {
    ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    service.schedule(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("schedule: " + dateFormat.format(new Date()));
    }, 1000, TimeUnit.MILLISECONDS);
    System.out.println(dateFormat.format(new Date()));
}

Callable有返回值,延迟一秒后执行

private void scheduleCallable() throws Exception {
    ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    ScheduledFuture<String> future = service.schedule(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("schedule: " + dateFormat.format(new Date()));
        return "success";
    }, 1000, TimeUnit.MILLISECONDS);
    System.out.println("return: " + future.get() + " " + dateFormat.format(new Date()));
}

2.2 循环调度

scheduleAtFixedRate()
固定频率,延迟一秒,然后每两秒执行一次

相对于任务执行的开始时间

public static void main (String[] args) {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    service.scheduleAtFixedRate(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("scheduleAtFixedRate: " + dateFormat.format(new Date()));
    }, 1000, 2000, TimeUnit.MILLISECONDS);
    System.out.println(dateFormat.format(new Date()));
}

输出:

15:20:09
scheduleAtFixedRate: 15:20:13
scheduleAtFixedRate: 15:20:16
scheduleAtFixedRate: 15:20:19

scheduleWithFixedDelay()
固定延迟,延迟一秒,然后每两秒执行一次

相对于任务执行的完成时间

public static void main (String[] args) {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    service.scheduleWithFixedDelay(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("scheduleWithFixedDelay: " + dateFormat.format(new Date()));
    }, 1000, 2000, TimeUnit.MILLISECONDS);
    System.out.println(dateFormat.format(new Date()));
}

输出:

15:21:09
scheduleWithFixedDelay: 15:21:13
scheduleWithFixedDelay: 15:21:18
scheduleWithFixedDelay: 15:21:23

3. Spring Task

Spring Task包含在spring-boot-starter基础模块中了,所有不需要增加额外的依赖。使用的时候需要在main方法上添加注解 @EnableScheduling,通过@Scheduled注解使用。

public @interface Scheduled {
	String CRON_DISABLED = "-";
	// 使用cron表达式
    String cron() default "";
	
	// 时区
    String zone() default "";

	// 固定延迟,每次执行任务之后间隔多久再次执行该任务。
    long fixedDelay() default -1L;
    String fixedDelayString() default "";

	// 固定频率,每隔多少时间就启动任务,不管该任务是否启动完成。
    long fixedRate() default -1L;
    String fixedRateString() default "";

	//初次执行任务之前需要等待的时间
    long initialDelay() default -1L;
    String initialDelayString() default "";
}

使用

@Component
public class ScheduledTest {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTest.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

	// 固定频率
    @Scheduled(fixedRate = 1000)
    public void fixedRateTest() throws InterruptedException {
        Thread.sleep(2000);
        log.info("fixedRateTest: {}", dateFormat.format(new Date()));
    }

	// 固定延迟
    @Scheduled(fixedDelay = 1000)
    public void fixedDelayTest() throws InterruptedException {
        Thread.sleep(2000);
        log.info("fixedDelayTest: {}", dateFormat.format(new Date()));
    }

	// cron表达式时间定义规则,当方法的执行时间超过任务调度频率时,调度器会在下个周期执行。
    @Scheduled(cron = "0/1 * * * * *")
    public void cronTest() throws InterruptedException {
        Thread.sleep(2000);
        log.info("cronTest: {}", dateFormat.format(new Date()));
    }
}

默认Spring配置中调度为单线程,线程数为1。当有多个定时任务的时候,效率很低,可以改为多线程,每个线程执行一个调度任务。

修改application.yml,将线程数为10

spring:
  task:
    scheduling:
      pool:
        size: 10

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

4. 总结

调度机制都可分为固定频率和固定延迟。
固定频率为相对于任务执行的开始时间,固定延迟为相对于任务执行的完成时间。

5. Cron 表达式

Cron表达式是一个字符串,包括6~7个时间元素。

Cron语法格式

Seconds Minutes Hours DayofMonth Month DayofWeek

Cron格式中每个时间元素的说明

时间元素可出现的字符有效数值范围
Seconds, - * /0-59
Minutes, - * /0-59
Hours, - * /0-23
DayofMonth, - * / ? L W0-31
Month, - * /1-12
DayofWeek, - * / ? L #1-7或SUN-SAT

Cron格式中特殊字符说明

字符作用举例
,列出枚举值在Minutes域使用5,10,表示在5分和10分各触发一次
-表示触发范围在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次
*匹配任意值在Minutes域使用*, 表示每分钟都会触发一次
/起始时间开始触发,每隔固定时间触发一次在Minutes域使用5/10,表示5分时触发一次,每10分钟再触发一次
?在DayofMonth和DayofWeek中,用于匹配任意值在DayofMonth域使用?,表示每天都触发一次
#在DayofMonth中,确定第几个星期几1#3表示第三个星期日
L表示最后在DayofWeek中使用5L,表示在最后一个星期四触发
W表示有效工作日(周一到周五)在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日4日触发一次

参考:
Java定时调度机制 - Timer
Java定时调度机制 - ScheduledExecutorService
Spring Scheduling Tasks Guide
Spring @Scheduled 文档
springboot集成schedule(深度理解)
spring boot 2X中@Scheduled实现定时任务及多线程配置
mall整合SpringTask实现定时任务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值