计划任务功能在应用程序及其常见,使用Spring Boot的@Scheduled 注解可以很方便的定义一个计划任务。然而在实际开发过程当中还应该注意它的计划任务默认是放在容量为1个线程的线程池中执行,即任务与任务之间是串行执行的。如果没有注意这个问题,开发的应用可能出现不按照设定计划执行的情况。本文将介绍几种增加定时任务线程池的方式。

验证Spring Boot计划任务中的“坑”

其实是验证Spring Boot 中执行任务的线程只有1个。先定义两个任务,输出任务执行时的线程ID,如果ID一样则认为这两个任务是一个线程执行的。这意味着,某个任务即使到达了执行时间,如果还有任务没有执行完,它也不能开始。

package com.example.springbootlearning.schedule;

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; @Component
@EnableScheduling
public class ScheduleDemo { @Scheduled(fixedRate = 5000)
public void work1() throws InterruptedException {
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getId() + ": work1 Begin.");
Thread.sleep(3000L);
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getId() + ": work1 End.");
} @Scheduled(fixedRate = 5000)
public void work2() throws InterruptedException {
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getId() + ": work2 Begin.");
Thread.sleep(3000L);
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getId() + ": work2 End.");
} }

部分输出:

1577949369664:20: work2 Begin.
1577949371665:20: work2 End.
1577949371665:20: work1 Begin.
1577949373665:20: work1 End.
......

以上代码定义了两个任务work1和work2,都是每5000ms执行一次,输出开始执行时间戳,线程ID,和结束时间戳。从输出可以看出,执行两个work的线程是相同的(ID都是20),work2先抢占线程资源,work2 执行结束之后 work1才开始执行。这导致:本来按计划work1应该每5秒执行一次,现在却变成了每6秒执行一次。如图:

让计划任务在不同的线程中执行

要让计划任务在不同的线程中执行,只需要自己去定义执行任务的线程池就可以了。具体操作就是添加一个实现了 SchedulingConfigurer 接口的配置类,然后在配置类里面设置线程池。具体代码如下:

package com.example.springbootlearning.schedule;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; @Configuration
public class ScheduleConfiguration implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { // 将 Scheduler 设置为容量为 5 的计划任务线程池。
scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
}
}

Spring Boot 版本2.1.0之后,可以直接在配置文件中设置 spring.task.scheduling.pool.size 的值来配置计划任务线程池的容量,而不需要额外再写一个类。

spring.task.scheduling.pool.size=5

输出:

1577953149351:20: work2 Begin.
1577953149352:21: work1 Begin.

从输出可以看出,两个任务可以认为是同时开始的(相差1毫秒),分别在ID为20和ID为21的线程中执行。如图:

小结

Spring Boot 定时任务的线程数默认是1,直接使用它调用定时任务会导致不同的任务无法并行执行。因此在使用它之前应该根据需求在application.properties中修改它的线程池容量;或者实现SchedulingConfigurer接口,并在重写的方法中自定义一个线程池。

Spring Boot 计划任务中的一个“坑”的更多相关文章

  1. spring boot: 计划任务@ EnableScheduling和@Scheduled

    spring boot: 计划任务@ EnableScheduling和@Scheduled @Scheduled中的参数说明 @Scheduled(fixedRate=2000):上一次开始执行时间 ...

  2. Spring Boot 2.2 增加了一个新功能,启动飞起~

    前几天栈长分享了一个好玩的框架:一个比Spring Boot快44倍的Java框架!,是不是感觉 Spring Boot 略慢?今天讲一下 Spring Boot 添加的这个新特性,可以大大提升 Sp ...

  3. spring boot 学习(六)spring boot 各版本中使用 log4j2 记录日志

    spring boot 各版本中使用 log4j2 记录日志 前言 Spring Boot中默认日志工具是 logback,只不过我不太喜欢 logback.为了更好支持 spring boot 框架 ...

  4. Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题【转】

    Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题 http://blog.didispace.com/Spring-Boot-And-Feign- ...

  5. Spring Boot源码中模块详解

    Spring Boot源码中模块详解 一.源码 spring boot2.1版本源码地址:https://github.com/spring-projects/spring-boot/tree/2.1 ...

  6. 最详细的 Spring Boot 多模块开发与排坑指南

    创建项目 创建一个 SpringBoot 项目非常的简单,简单到这里根本不用再提.你可以在使用 IDEA 新建项目时直接选择 Spring Initlalize 创建一个 Spring Boot 项目 ...

  7. andriod8.1.0源码编译中的一个坑-package com.sun.javadoc does not exist

    这里记录编译过程中的一个坑!!! 编译过程中出现了下面的报错 external/doclava/src/com/google/doclava/ClassInfo.java:20: error: pac ...

  8. 阿里P7级教你如何在Spring Boot应用程序中使用Redis

    在Spring Boot应用程序中使用Redis缓存的步骤: 1.要获得Redis连接,我们可以使用Lettuce或Jedis客户端库,Spring Boot 2.0启动程序spring-boot-s ...

  9. spring boot开发,jar包一个一个来启动太麻烦了,写一个bat文件一键启动

    spring boot开发,jar包一个一个来启动太麻烦了,写一个bat文件一键启动 @echo offcd D:\workProject\bushustart cmd /c "title ...

随机推荐

  1. Angular:使用前端proxy代理解决跨域问题

    ①在项目文件的根目录下新建文件proxy.config.json { "/": { "target": "http://127.0.0.1:3000& ...

  2. Vue:对axios进行简单的二次封装

    主要做3点: 1.配置一个请求地址前缀 2.请求拦截(在请求发出去之前拦截),给所有的请求都带上 token 3.拦截响应,遇到 token 不合法则报错 // 对 axios 的二次封装 impor ...

  3. window+nginx+php

    今天在Windows上配置了下nginx,看了不少其他大牛们记录的博客,自己也操作了一番,记录一下备忘. nginx download: http://nginx.org/en/download.ht ...

  4. 一文带你彻底了解大数据处理引擎Flink内存管理

    摘要: Flink是jvm之上的大数据处理引擎. Flink是jvm之上的大数据处理引擎,jvm存在java对象存储密度低.full gc时消耗性能,gc存在stw的问题,同时omm时会影响稳定性.同 ...

  5. Kafka高性能的原理

    Kafka高性能的原理 高性能,高并发,高可用 使用了NIO技术.高并发. 顺序读写.硬盘的顺序读写性能要高于内存的随机读写. 跳表设计. 稀疏索引.index文件里面有部分offset的位置. 使用 ...

  6. 【Go语言绘图】图片添加文字(一)

    前一篇讲解了利用gg包来进行图片旋转的操作,这一篇我们来看看怎么在图片上添加文字. 绘制纯色背景 首先,我们先绘制一个纯白色的背景,作为添加文字的背景板. package main import &q ...

  7. k8s之深入解剖Pod(一)

    上文说了一下k8s的简单使用,接下来就让我们来具体深入了解一下Pod.为了避免篇幅太长,所以会分成几篇. 目录: Pod定义详解 静态Pod Pod容器共享Volume 一.Pod定义详解 先看一个简 ...

  8. Java 面试知识点【背诵版 240题 约7w字】

    -- 转载自牛客网 是瑶瑶公主吖 Java 基础 40 语言特性 12 Q1:Java 语言的优点? ① 平台无关性,摆脱硬件束缚,"一次编写,到处运行". ② 相对安全的内存管理 ...

  9. Arduino PROGMEM 从程序空间读取float值的方法

    方法: 使用avr-libc提供的宏定义: #define pgm_read_float_near(address_short) __LPM_float((uint16_t)(address_shor ...

  10. 5分钟教你在Linux下安装VMware

    如果我们只有一台笔记本,又想要搭建一个小集群,怎么办?虚拟机帮你实现梦想,市面上较为常用的虚拟机软件有VMware.VirtualBox.Xen.KVM.hyper-v等,本文主要介绍如何在Linux ...