谈谈任务调度的四种实现方式

一、什么是任务调度

任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务,本文会介绍Timer、ScheduledExecutor、Quartz、Spring Boot中的调度模块使用。

二、Timer

Timer任务调度的核心类是 Timer 和 TimerTask。其中Timer负责设定TimerTask的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的run方法,然后将其丢给 Timer 去执行即可。

下面的例子每隔半秒执行run中的方法,输出当前时间和name。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Timer timer = new Timer();
        timer.schedule(new TestTimerTask("test"), 0, 500);
    }
    static class TestTimerTask extends TimerTask {
        private String name;
        public TestTimerTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println(LocalDateTime.now()+"-----run----" + name);
        }
    }
}

运行结果:

2020-05-19T09:19:11.301-----run----test
2020-05-19T09:19:11.741-----run----test
2020-05-19T09:19:12.241-----run----test
2020-05-19T09:19:12.742-----run----test
2020-05-19T09:19:13.242-----run----test
2020-05-19T09:19:13.742-----run----test
2020-05-19T09:19:14.242-----run----test
2020-05-19T09:19:14.743-----run----test
2020-05-19T09:19:15.243-----run----test
2020-05-19T09:19:15.743-----run----test
2020-05-19T09:19:16.244-----run----test
2020-05-19T09:19:16.743-----run----test
2020-05-19T09:19:17.243-----run----test

因为Timer底层是使用一个单线来实现多个Timer任务处理的,所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而某一个任务的延迟或者异常会影响到之后的任务。如下面这段,当test2抛出异常后,test也会终止。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Timer timer = new Timer();
        timer.schedule(new TestTimerTask("test"), 0, 500);
        timer.schedule(new TestTimerTask("test2"), 0, 500);
    }
    static class TestTimerTask extends TimerTask {
        private String name;
        private int count ;
        public TestTimerTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            if ("test2".equals(name)){
                count++;
                if (count>5){
                    throw  new NullPointerException();
                }
            }
            System.out.println(LocalDateTime.now()+"-----run----" + name);
        }
    }
}

三、ScheduledExecutor

鉴于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor。其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

下面这段是每隔一秒执行一次任务。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleWithFixedDelay(new TestRunnable("test1"),0,1, TimeUnit.SECONDS);
    }
    static class TestRunnable  implements Runnable{
        private String name;
        public TestRunnable(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println(LocalDateTime.now()+"-----run----" + name);
        }
    }
}

运行结果:

2020-05-19T09:30:07.005-----run----test1
2020-05-19T09:30:08.005-----run----test1
2020-05-19T09:30:09.006-----run----test1
2020-05-19T09:30:10.006-----run----test1

ScheduledExecutorService中常用的调度方法是scheduleAtFixedRate和scheduleWithFixedDelay。

scheduleAtFixedRate每次执行时间为上一次任务开始起向后推一个时间间隔,即每次执行时间为 :initialDelay, initialDelay+period, initialDelay+2*period, …。

scheduleWithFixedDelay每次执行时间为上一次任务结束起向后推一个时间间隔,即每次执行时间为:initialDelay, initialDelay+executeTime+delay, initialDelay+2executeTime+2delay。由此可见,scheduleAtFixedRate是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度。

四、Quartz

Quartz是一个由OpenSymphony组织开源的java调度框架。特点如下。

(1)具有强大的调度功能,很容易与spring集成,形成灵活可配置的调度功能。
(2)调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失。
(3)灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
(4)分布式与集群能力。

 <dependency>
     <groupId>org.quartz-scheduler</groupId>
     <artifactId>quartz</artifactId>
     <version>2.3.0</version>
 </dependency>

下面这段还是每隔1秒打印当前时间。

public class Main   {
    public static void main(String[] args) throws SchedulerException {
        JobDetail jobDetail =JobBuilder.newJob(SimpleQuartzJob.class)
                .withIdentity("testJob","group").build();
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("testTrigger", "group")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1).
                                repeatForever()).build();
 
        StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = stdSchedulerFactory.getScheduler();
        scheduler.start();
        scheduler.scheduleJob(jobDetail,trigger);
    }
}

public class SimpleQuartzJob implements Job {
    public SimpleQuartzJob() {}
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println(LocalDateTime.now()+"   "+context.getJobDetail().getKey().getName());
    }
}

Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。

另外Quartz也支持cron表达式,如下,*/1 * * * * ?为每隔1秒执行一次。

public class Main   {
    public static void main(String[] args) throws SchedulerException {
        JobDetail jobDetail =JobBuilder.newJob(SimpleQuartzJob.class)
                .withIdentity("testJob","group").build();
//        SimpleTrigger trigger = TriggerBuilder.newTrigger()
//                .withIdentity("testTrigger", "group")
//                .startNow()
//                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
//                        .withIntervalInSeconds(1).
//                                repeatForever()).build();
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("testTrigger", "group")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("*/1 * * * * ?"))
                .build();
        StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = stdSchedulerFactory.getScheduler();
        scheduler.start();
        scheduler.scheduleJob(jobDetail,trigger);
    }
}

五、Spring Boot中任务调度

在Spring Boot中开发定时任务需要在启动类上增加一个@EnableScheduling注解来开启定时任务功能。
在这里插入图片描述
其次,使用@Component注解标注一个类,在其需要定时执行的方法上添加@Scheduled注解。

@Component
public class TestScheduled {
    @Scheduled(fixedDelay = 3000)
    public  void test1(){
        System.out.println(LocalDateTime.now()+"  fixedDelay");
    }
    @Scheduled(fixedRate = 5000)
    public  void test2(){
        System.out.println(LocalDateTime.now()+"  fixedRate");
    }
    @Scheduled(cron = "*/1 * * * * ?")
    public  void test3(){
        System.out.println(LocalDateTime.now()+"  cron");
    }
}
@Scheduled中的参数如下:

	@Scheduled(fixedDelay = 3000):上次执行完毕后时间后3秒再次执行。
	@Scheduled(fixedRate = 5000):上次开始执行时间点后5秒再次执行。
	@Scheduled(cron = "*/1 * * * * ?"):按照cron表达式规则执行。
  
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章