SpringBoot学习四:定时任务和异步调用

相关文章

SpringBoot 学习一

SpringBoot 学习二:操作数据库

SpringBoot学习三:异常处理和记录日志

前言

今天来学习一下通过 SpringBoot 来实现一个定时任务和异步调用。

定时任务

在 Spring 中 可以通过 @EnableScheduling @Scheduled 这两个注解来实现我们的定时任务,接下来看下这两个注解:

@EnableScheduling

在配置类上标注了 @EnableScheduling 注解后,即表示 Spring 开启了定时任务,在 Spring 容器中标注了  @Scheduled 注解的方法都会被检测到,会进行执行;如果需要更多更加细粒度的控制,配置类可以实现 SchedulingConfigurer 类来进行实现,如下所示:

@Configuration
@EnableScheduling
public class MyScheduleTaskConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
//        taskRegistrar.setCronTasks();
//        taskRegistrar.setFixedDelayTasks();
//        taskRegistrar.setTriggerTasksList();
    }

    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
         return Executors.newScheduledThreadPool(100);
     }
}

 @Scheduled

在 Spring 中开启了定时任务后,就可以使用  @Scheduled  这个注解来定义需要定时执行的方法了,接下来看下它的一个定义:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    // cron 表达式
	String cron() default "";
    // 时区
	String zone() default "";
    // 时间间隔 单位毫秒
	long fixedDelay() default -1;
    // 时间间隔,单位毫秒,使用 String 表示
	String fixedDelayString() default "";
    // 时间间隔 单位毫秒
	long fixedRate() default -1;
    // 时间间隔,单位毫秒,使用 String 表示
	String fixedRateString() default "";
    // 初始化时间,即任务在多少秒后开始执行第一次
	long initialDelay() default -1;
    // 初始化时间,即任务在多少秒后开始执行第一次,使用 String 表示
	String initialDelayString() default "";

}


接下来,就来测试一下 cron ,fixedDelay 和 fixedRate 这三个属性:

cron 

cron 属性,即使用 cron 表达式来控制任务的定时执行,如下所示:

/**
 * 定时任务
 */
@Component
public class ScheduledTask {

    private static final DateFormat dateFormat = new SimpleDateFormat("HH:m:ss");

    @Scheduled(cron = "0/5 * * * * ?")
    public void printTime3() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
    }
}

启动程序,查看控制台打印,会 5 秒打印一次:

time: 14:19:00
time: 14:19:05
time: 14:19:10
time: 14:19:15

如果,任务在 5 秒之内已经执行完毕,会怎么样呢?

    @Scheduled(cron = "0/5 * * * * ?")
    public void printTime3() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(2000);
    }

time: 14:20:50
time: 14:20:55
time: 14:21:00
time: 14:21:05

由打印的结果看到,如果任务在间隔时间之内已经执行完毕,则下一个任务也会等到时间间隔结束后才会执行下一次任务。

 

如果,任务在 5 秒之内还没有执行完,则会怎么样呢?

    @Scheduled(cron = "0/5 * * * * ?")
    public void printTime3() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(8000);
    }

time: 14:28:25
time: 14:28:35
time: 14:28:45
time: 14:28:55

由打印结果可以看到,如果任务在时间间隔之内没有执行完毕,则下一个任务会留到下一个周期才会执行,因为 Spring 在处理使用cron表达式这种定时任务时,它会在任务开始时判断任务是否可以执行,如果可以则执行,如果不可以,那么它将不执行此次任务,等待下一次执行。

fixedDelay

fixedDelay 属性表示的是从上一个任务结束到下一个任务开始之间的时间间隔,如果上一个任务执行超时了,下一个任务会等待上一个任务执行完毕后才执行:

    @Scheduled(fixedDelay = 5000)
    public void printTime2() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
    }

time: 14:38:29
time: 14:38:34
time: 14:38:39
time: 14:38:44
time: 14:38:49

如果,任务在时间间隔内执行完毕:

    @Scheduled(fixedDelay = 5000)
    public void printTime2() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(2000);
    }

每隔 7 秒打印一次

time: 14:40:24
time: 14:40:31
time: 14:40:38
time: 14:40:45
time: 14:40:52

从打印结果可以看到,如果任务在时间间隔之内已经执行完毕,下一个任务等待的时间将会是任务执行的时间+时间间隔;

如果,任务在之间间隔之内没有执行完毕:

    @Scheduled(fixedDelay = 5000)
    public void printTime2() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(8000);
    }

每隔 13 秒打印一次

time: 14:44:20
time: 14:44:33
time: 14:44:46
time: 14:44:59
time: 14:45:12

从打印结果可以看到,如果任务在时间间隔之内还没有执行完毕,下一个任务等待的时间将会是任务执行的时间+时间间隔;

从上面两个例子可以看到,fixedDelay  主要关注的是上一个任务结束的时间到下一个任务开始的时间之间的时间间隔,也就是下一个任务开始执行前需要等待的时间=上一个任务执行时间+时间间隔

fixedRate

fixedRate 表示的是两个任务开始执行的时间间隔,

    @Scheduled(fixedRate = 5000)
    public void printTime() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
    }

time: 14:53:38
time: 14:53:43
time: 14:53:48
time: 14:53:53
time: 14:53:58

和上面的 cron 和 fixedDelay 一样,都是 5 秒执行一次。

如果,任务在时间间隔之内已经执行完毕,如下:

    @Scheduled(fixedRate = 5000)
    public void printTime() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(2000);
    }

time: 14:55:04
time: 14:55:09
time: 14:55:14
time: 14:55:19
time: 14:55:24

可以看到,这里就和 fixedDelay 不一样了,fixedDelay 下一个任务等待的时间等于上一个任务执行时间+时间间隔,而这里,从打印的结果可以看到,如果上一个任务在时间间隔之内已经执行完毕,则下一个任务还是会等待到时间间隔结束后才会执行。说明,fixedRate 表示的时间间隔是两个任务开始的时间间隔。

如果,任务在时间间隔之内没有执行完毕;

    @Scheduled(fixedRate = 5000)
    public void printTime() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(8000);
    }

time: 15:0:24
time: 15:0:32
time: 15:0:40
time: 15:0:48
time: 15:0:56

从打印结果可以看到,如果任务在时间间隔之内还没有执行完毕,则下一个任务会等待上一个任务执行完毕后才开始执行,此时时间间隔不再是 5秒而是8秒。

结论

1、fixedRate 强调的是上一次任务的开始时间 到 下一次任务的开始时间的间隔
2、fixedDelay 调的是上一次任务的结束时间 到 下一次任务的开始时间的间隔
3、cron表达式配置了在哪一刻执行任务,会在任务开始时判断任务是否可以执行,如果能则执行,不能则会跳过本次执行

异步调用

在 Spring Boot 中通过 @EnableAsync@Async 这两个注解来实现异步调用的,接下来看下这两个注解:

@EnableAsync

@EnableAsync 该注解主要是用来开启Spring 异步调用的,用在配置类上,

@SpringBootApplication
@EnableAsync
public class MyspringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyspringbootApplication.class, args);
    }
}

@Async

当 Spring 开启了 异步调用后,就可以使用 @Async 定义需要异步执行的方法了,

/**
 * 异步调用任务
 */
@Component
public class AsyncTask {

    private Random random = new Random();

    @Async
    public void task1() throws InterruptedException {
        System.out.println("任务一开始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任务一耗时:" + (System.currentTimeMillis() - start));
        System.out.println("任务一完成...");
    }

    @Async
    public void task2() throws InterruptedException {
        System.out.println("任务二开始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任务二耗时:" + (System.currentTimeMillis() - start));
        System.out.println("任务二完成...");
    }

    @Async
    public void task3() throws InterruptedException {
        System.out.println("任务三开始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任务三耗时:" + (System.currentTimeMillis() - start));
        System.out.println("任务三完成...");
    }
}

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyspringbootApplication.class)
public class MyTest {

    @Autowired
    private AsyncTask task;

    @Test
    public void test() throws InterruptedException {
        task.task1();
        task.task2();
        task.task3();
        Thread.sleep(60 * 1 * 1000);
    }
}

任务一开始...
任务三开始...
任务二开始...
任务三耗时:2018
任务三完成...
任务一耗时:4027
任务一完成...
任务二耗时:9364
任务二完成...

 

如果不想使用默认的线程池,我们还可以自定义线程池,只需要实现 AsyncConfigurer 即可:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Bean(name = "taskExecutor")
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        // 关闭资源
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

使用自定义线程池:

    @Async("taskExecutor")
    public void task1() throws InterruptedException {
        System.out.println("任务一开始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任务一耗时:" + (System.currentTimeMillis() - start));
        System.out.println("任务一完成...");
    }

 

在线程执行的时候,可以有返回值 Future,我们可以返回 AsyncResult:

    @Async("taskExecutor")
    public Future<String> test4(){
        System.out.println("执行任务,返回结果");
        return new AsyncResult<>("任务执行结果:5");
    }

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyspringbootApplication.class)
public class MyTest {

    @Autowired
    private AsyncTask task;

    @Test
    public void test2() throws InterruptedException, ExecutionException, TimeoutException {
        Future<String> future = task.test4();
        System.out.println("任务是否完成:" + future.isDone());
        String result = future.get(5, TimeUnit.SECONDS);
        System.out.println(result);
        System.out.println("任务是否完成:" + future.isDone());
    }
}

结果:

任务是否完成:false
执行任务,返回结果
任务执行结果:5
任务是否完成:true

以上就是通过 SpringBoot 来操作定时任务和异步调用。

转载于:https://my.oschina.net/mengyuankan/blog/2223408

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值