首页 > 其他分享 >Spring Boot 定时任务单线程和多线程配置

Spring Boot 定时任务单线程和多线程配置

时间:2023-05-31 23:02:45浏览次数:36  
标签:单线程 Spring 任务 线程 org import 定时 多线程 public

第一种:把参数配置到.properties文件中:

代码:

package com.accord.task;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
/**
 * 从配置文件加载任务信息
 * @author 王久印
 * 2018年3月1日
 */
@Component
public class ScheduledTask {
 
  private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
 
  //@Scheduled(fixedDelayString = "${jobs.fixedDelay}")
  @Scheduled(fixedDelayString = "2000")
  public void getTask1() {
    System.out.println("任务1,从配置文件加载任务信息,当前时间:" + dateFormat.format(new Date()));
  }
 
  @Scheduled(cron = "${jobs.cron}")
  public void getTask2() {
    System.out.println("任务2,从配置文件加载任务信息,当前时间:" + dateFormat.format(new Date()));
  }
}

application.properties文件:

jobs.fixedDelay=5000
jobs.cron=0/5 * *  * * ?

SpringBootCron2Application.java中:

package com.accord;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
 
@SpringBootApplication
@EnableScheduling
public class SpringBootCron2Application {
	public static void main(String[] args) {
		SpringApplication.run(SpringBootCron2Application.class, args);
	}
}

注:@EnableScheduling  这个一定要加上;否则,不会定时启动任务!

@Scheduled中的参数说明:

@Scheduled(fixedRate=2000):上一次开始执行时间点后2秒再次执行;
 
@Scheduled(fixedDelay=2000):上一次执行完毕时间点后2秒再次执行;
 
@Scheduled(initialDelay=1000, fixedDelay=2000):第一次延迟1秒执行,然后在上一次执行完毕时间点后2秒再次执行;
 
@Scheduled(cron="* * * * * ?"):按cron规则执行。

在线Cron表达式生成器:http://cron.qqe2.com/

 



第二种定时任务:单线程和多线程

1、创建定时任务:

package com.accord.task;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
/**
 * 构建执行定时任务
 * @author 王久印
 * 2018年3月1日
 * TODO
 */
@Component
public class ScheduledTask2 {
 
    private Logger logger = LoggerFactory.getLogger(ScheduledTask2.class);
 
    private int fixedDelayCount = 1;
    private int fixedRateCount = 1;
    private int initialDelayCount = 1;
    private int cronCount = 1;
 
    @Scheduled(fixedDelay = 5000)        //fixedDelay = 5000表示当前方法执行完毕5000ms后,Spring scheduling会再次调用该方法
    public void testFixDelay() {
        logger.info("===fixedDelay: 第{}次执行方法", fixedDelayCount++);
    }
 
    @Scheduled(fixedRate = 5000)        //fixedRate = 5000表示当前方法开始执行5000ms后,Spring scheduling会再次调用该方法
    public void testFixedRate() {
        logger.info("===fixedRate: 第{}次执行方法", fixedRateCount++);
    }
 
    @Scheduled(initialDelay = 1000, fixedRate = 5000)   //initialDelay = 1000表示延迟1000ms执行第一次任务
    public void testInitialDelay() {
        logger.info("===initialDelay: 第{}次执行方法", initialDelayCount++);
    }
 
    @Scheduled(cron = "0 0/1 * * * ?")  //cron接受cron表达式,根据cron表达式确定定时规则
    public void testCron() {
        logger.info("===initialDelay: 第{}次执行方法", cronCount++);
    }
 
}

使用 @Scheduled来创建定时任务 这个注解用来标注一个定时任务方法。 
通过看 @Scheduled源码可以看出它支持多种参数:
    (1)cron:cron表达式,指定任务在特定时间执行;
    (2)fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
    (3)fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
    (4)fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
    (5)fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
    (6)initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
    (7)initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
    (8)zone:时区,默认为当前时区,一般没有用到。

2、开启定时任务:

package com.accord;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
 
@SpringBootApplication
@EnableScheduling
public class SpringBootCron2Application {
	public static void main(String[] args) {
		SpringApplication.run(SpringBootCron2Application.class, args);
	}
}

注:这里的 @EnableScheduling  注解,它的作用是发现注解 @Scheduled的任务并由后台执行。没有它的话将无法执行定时任务。
引用官方文档原文:
@EnableScheduling ensures that a background task executor is created. Without it, nothing gets scheduled.

3、执行结果(单线程)

就完成了一个简单的定时任务模型,下面执行springBoot观察执行结果:

Spring Boot 定时任务单线程和多线程配置_spring

从控制台输入的结果中我们可以看出所有的定时任务都是在同一个线程池用同一个线程来处理的,那么我们如何来并发的处理各定时任务呢,请继续向下看。
4、多线程处理定时任务:

看到控制台输出的结果,所有的定时任务都是通过一个线程来处理的,我估计是在定时任务的配置中设定了一个SingleThreadScheduledExecutor,于是我看了源码,从ScheduledAnnotationBeanPostProcessor类开始一路找下去。果然,在ScheduledTaskRegistrar(定时任务注册类)中的ScheduleTasks中又这样一段判断:

if (this.taskScheduler == null) {
	this.localExecutor = Executors.newSingleThreadScheduledExecutor();
	this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}

这就说明如果taskScheduler为空,那么就给定时任务做了一个单线程的线程池,正好在这个类中还有一个设置taskScheduler的方法:

public void setScheduler(Object scheduler) {
	Assert.notNull(scheduler, "Scheduler object must not be null");
	if (scheduler instanceof TaskScheduler) {
		this.taskScheduler = (TaskScheduler) scheduler;
	}
	else if (scheduler instanceof ScheduledExecutorService) {
		this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));
	}
	else {
		throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());
	}
}

这样问题就很简单了,我们只需用调用这个方法显式的设置一个ScheduledExecutorService就可以达到并发的效果了。我们要做的仅仅是实现SchedulingConfigurer接口,重写configureTasks方法就OK了;

package com.accord.task;
 
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 ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        //设定一个长度10的定时任务线程池
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

5、执行结果(并发)

Spring Boot 定时任务单线程和多线程配置_线程池_02

 

 



第三、多线程定时任务

参考@EnableScheduling注解中的注释

Spring Boot 定时任务单线程和多线程配置_spring_03

如图,我们只需要添加一个配置类并实现SchedulingConfigurer接口,然后在configureTasks方法中设置线程池就行了。

ScheduleConfig.java代码:

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
	
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

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

还可以如下:

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
 
/**
 * spring定时任务 多任务多线程配置
 */
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        //开启5个线程,执行现有的定时任务
        taskScheduler.setPoolSize(5);
        //设置线程池中,线程的命名前缀,方便识别那些线程是定时任务的
        taskScheduler.setThreadNamePrefix("cms-job-");
        taskScheduler.initialize();
        scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
    }
}

 

 



第四、单独引入线程池,然后每个定时任务都加上对应线程池

package com.example.springbootscheduled.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author Song Jiangtao
 * @date 2021/12/22
 * @description:
 */
@Configuration
@EnableAsync
public class TaskConfig {

    /**
     * 默认线程数
     */
    private static final int corePoolSize = 10;
    /**
     * 最大线程数
     */
    private static final int maxPoolSize = 100;
    /**
     * 允许线程空闲时间(单位:默认为秒),十秒后就把线程关闭
     */
    private static final int keepAliveTime = 10;
    /**
     * 缓冲队列数
     */
    private static final int queueCapacity = 200;
    /**
     * 线程池名前缀
     */
    private static final String threadNamePrefix = "task-thread-";

    /**
     * bean的名称,默认为 首字小写 方法名
     */
    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor getThread() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(keepAliveTime);
        executor.setKeepAliveSeconds(queueCapacity);
        executor.setThreadNamePrefix(threadNamePrefix);
        //线程池拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

每个定时任务添加注解 @Async("taskExecutor")

@Async("taskExecutor")
@Scheduled(fixedRate = 2000)
public void currentTime(){
    log.info("做点什么");
}

 

 



第五、原生JDK 实现多任务多线程执行,可以借助ScheduledThreadPool。

核心代码如下

//JDK原生代码片段1 创建线程池
//一个任务一个线程池,且线程池中只配置一个执行线程,即实现单任务单线程执行
//如上,多个任务分别创建多个线程池对象ScheduledExecutorService ,即可实现多任务多线程
private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
 
//JDK原生代码片段2 执行定时任务
// 2000代表首次延迟2秒执行
// 30*1000表示执行完成后,30S后再次运行定时任务
// scheduleWithFixedDelay 表示A任务执行完成后,30S后才将下次执行的A任务加入线程池
// scheduleAtFixedRate 表示以A任务开始执行时计时,30S后将下次执行的A任务加入线程队列,此时如果线程池是多个线程,会导致A任务并行执行,慎重
scheduledExecutorService.scheduleWithFixedDelay(() -> {
			try {
				execTask()//此处执行你的定时任务
			} catch (Throwable e) {
				log.error("充分捕获异常,保证定时任务不中断", e);
			}
		}, 2000,30*1000, TimeUnit.MILLISECONDS);

 

 

 

 

标签:单线程,Spring,任务,线程,org,import,定时,多线程,public
From: https://blog.51cto.com/lenglingx/6390814

相关文章

  • Spring Boot缓存注解使用案例
    一、spring-boot-cache缓存1、pom<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.6.7</version></dependency>2、main启动类加入@EnableCachi......
  • SpringBoot集成Redis
    依赖包:<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><grou......
  • 《面试1v1》Spring基础
    我是javapub,一名Markdown程序员从......
  • Spring加载Bean有哪些方式
    转载:https://www.bilibili.com/video/BV1AL411B7Zp/?vd_source=46d50b5d646b50dcb2a208d3946b1598......
  • python获取threading多线程的return返回值
    转载:(15条消息)python获取threading多线程的return返回值_pythonthreading返回值_星火燎愿的博客-CSDN博客我们正常使用threading模块创建的线程是无法获取它所执行方法的返回值的;如:w=threading.Thread(target=worker,args=(i,))一、重写线程,获取线程return返回值要想获......
  • spring cloud 之 openfeign 记录(通过feign上传)
    今日搭建好nacos nacos踩坑记录迫不及待的进入了下一步,服务间的远程调用,就踩了一个小小的坑 我做的是一个阿里oss上传的服务!阿里oss服务个人可以有三个月试用,对新手非常的友好首先是一个openfeign编写上的问题@RequestMapping(value="/common/oss/download",meth......
  • Spring MVC官方文档学习笔记(二)之DispatcherServlet
    1.DispatcherServlet入门(1)SpringMVC是以前端控制器模式(即围绕着一个中央的Servelt,DispatcherServlet)进行设计的,这个DispatcherServlet为请求的处理提供了一个共用的算法,即它都会将实际的处理工作委托给那些可配置的组件进行执行,说白了,DispatcherServlet的作用就是统......
  • springboot整合log4j解决依赖冲突
    首先将web模块的日志排除<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion>......
  • SpringBoot的基础
    1、获取配置文件的值:@Value的方式yml配置文件redis:host:199.22.22.341.1、用法1:普通用法@Value("${redis.host}")privateStringparamName;结果1.2、用法2:冒号(:)的作用冒号(:)的作用:当获取的值没有值时,返回冒号后的预设值默认值@Value("${redis......
  • Java实战-基于JDK的LRU算法实现、优雅的实现代码耗时统计(Spring AOP、AutoCloseable
    场景Java中基于JDK的LRU算法实现LRU算法-缓存淘汰算法-Leastrecentlyused,最近最少使用算法根据数据的历史访问记录来进行淘汰数据,其核心思想是:如果有数据最近被访问过,那么将来被访问的几率也更高在Java中可以利用LinkedHashMap容器简单实现LRU算法LinkedHashMap底层就是用......