SpringBoot—自定义线程池及并发定时任务模板

关注微信公众号:CodingTechWork,一起学习进步。
在这里插入图片描述

介绍

  在项目开发中,经常遇到定时任务,今天通过自定义多线程池总结一下SpringBoot默认实现的定时任务机制。

定时任务模板

pom依赖

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	//线程池用到
	<dependency>
    	<groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>22.0</version>
    </dependency>
    //@Slf4j注解用到
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
        <version>1.18.4</version>
    </dependency>
</dependencies>

自定义线程池模板

package com.example.andya.demo.conf;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.*;

/**
 * @author Andya
 * @create 2020-05-29 14:08
 */
@Configuration
public class ThreadPoolConfig {

    public static String THREAD_NAME = "first-thread-pool-%d";
    public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2 + 1;
    public static int MAX_POOL_SIZE = 10;
    public static int QUEUE_SIZE = 10;

    /**
     * 自定义消费队列线程池
     *
     * @return
     */
    @Bean(value = "firstThreadPool")
    public ExecutorService buildFirstThreadPool() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(THREAD_NAME).build();

        /**
         * 1. CallerRunsPolicy :    这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。
         2. AbortPolicy :         对拒绝任务抛弃处理,并且抛出异常。
         3. DiscardPolicy :       对拒绝任务直接无声抛弃,没有异常信息。
         4. DiscardOldestPolicy : 对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。
         */
        ExecutorService threadPool = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                0L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(QUEUE_SIZE),
                threadFactory,
                new ThreadPoolExecutor.AbortPolicy());
        return threadPool;
    }
}

定时任务模板

package com.example.andya.demo.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author Andya
 * @create 2020-05-29 14:26
 */
@Service
@EnableScheduling
@Slf4j
public class TestThreadPool {
    @Resource(name = "firstThreadPool")
    private ExecutorService firstThreadPool;

    @Scheduled(cron = "0 * * * * *")
    public void test1SchedulerThreadPool() {
        final CountDownLatch countDownLatch = new CountDownLatch(5);
        log.info("Begin schedule-【1】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        for (int i = 0; i < 5; i++) {
            firstThreadPool.execute(() -> {
                countDownLatch.countDown();
                log.info("schedule-【1】 , threadPool info: {}, countDownLatch info: {}", firstThreadPool.toString(), countDownLatch.toString());
            });
        }
        try {
            countDownLatch.await(5, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            log.error("schedule-【1】timeout, {}", e.getMessage());
        } finally {
            log.info("schedule-【1】 multi-threading countDownLatch count: {}",  countDownLatch.getCount());
        }
        log.info("End schedule-【1】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

    @Scheduled(cron = "0 * * * * *")
    public void test2SchedulerThreadPool() {
        final CountDownLatch countDownLatch = new CountDownLatch(5);
        log.info("Begin schedule-【2】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        for (int i = 0; i < 5; i++) {
            firstThreadPool.execute(() -> {
                countDownLatch.countDown();
                log.info("schedule-【2】, threadPool info {}, countDownLatch info: {}", firstThreadPool.toString(), countDownLatch.toString());
            });
        }
        try {
            countDownLatch.await(5, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            log.error("schedule-【2】timeout, {}", e.getMessage());
        } finally {
            log.info("schedule-【2】 multi-threading countDownLatch count: {}",  countDownLatch.getCount());
        }
        log.info("End schedule-【2】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

运行结果

2020-05-29 15:45:00.011  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : Begin schedule-2】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.018  INFO 6900 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 5, queued tasks = 0, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 4]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-1] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 4, queued tasks = 0, completed tasks = 1], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 3]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-2] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 3, queued tasks = 0, completed tasks = 2], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 2]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-3] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 2, queued tasks = 0, completed tasks = 3], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 1]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-4] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 1, queued tasks = 0, completed tasks = 4], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 0]
2020-05-29 15:45:00.019  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : schedule-2】 multi-threading countDownLatch count: 0
2020-05-29 15:45:00.019  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : End schedule-2】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.020  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : Begin schedule-1】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.021  INFO 6900 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 5, queued tasks = 0, completed tasks = 5], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 4]
2020-05-29 15:45:00.023  INFO 6900 --- [t-thread-pool-5] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 4, queued tasks = 0, completed tasks = 6], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 3]
2020-05-29 15:45:00.024  INFO 6900 --- [t-thread-pool-6] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 2]
2020-05-29 15:45:00.024  INFO 6900 --- [t-thread-pool-7] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 1]
2020-05-29 15:45:00.025  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : schedule-1】 multi-threading countDownLatch count: 0
2020-05-29 15:45:00.025  INFO 6900 --- [t-thread-pool-8] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 0]
2020-05-29 15:45:00.025  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : End schedule-1】 startTime: 2020-05-29 15:45:00

从上述结果中可以看出,虽然是test1SchedulerThreadPool()test2SchedulerThreadPool()都是每分钟执行定时任务,但是明显两个方法没有并发执行,而是串行执行的。

并发定时器模板

通过增加一个配置类来并发执行定时任务。

package com.example.andya.demo.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executors;

/**
 * @author Andya
 * @create 2020-05-29 15:52
 */
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

运行结果

2020-05-29 15:53:00.005  INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool    : Begin schedule-2】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.015  INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : Begin schedule-1】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.019  INFO 3028 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 3]
2020-05-29 15:53:00.020  INFO 3028 --- [t-thread-pool-3] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 1]
2020-05-29 15:53:00.019  INFO 3028 --- [t-thread-pool-1] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 3]
2020-05-29 15:53:00.019  INFO 3028 --- [t-thread-pool-2] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 2]
2020-05-29 15:53:00.021  INFO 3028 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 6, queued tasks = 0, completed tasks = 4], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 4]
2020-05-29 15:53:00.021  INFO 3028 --- [t-thread-pool-4] c.e.andya.demo.service.TestThreadPool    : schedule-2, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 5, queued tasks = 0, completed tasks = 5], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 0]
2020-05-29 15:53:00.021  INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool    : schedule-2】 multi-threading countDownLatch count: 0
2020-05-29 15:53:00.022  INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool    : End schedule-2】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.022  INFO 3028 --- [t-thread-pool-5] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 4, queued tasks = 0, completed tasks = 6], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 3]
2020-05-29 15:53:00.022  INFO 3028 --- [t-thread-pool-6] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 2]
2020-05-29 15:53:00.024  INFO 3028 --- [t-thread-pool-7] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 2, queued tasks = 0, completed tasks = 8], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 1]
2020-05-29 15:53:00.024  INFO 3028 --- [t-thread-pool-8] c.e.andya.demo.service.TestThreadPool    : schedule-1, threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 2, queued tasks = 0, completed tasks = 8], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 0]
2020-05-29 15:53:00.024  INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : schedule-1】 multi-threading countDownLatch count: 0
2020-05-29 15:53:00.024  INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : End schedule-1】 startTime: 2020-05-29 15:53:00

可以从运行结果中看到,test1SchedulerThreadPool()test2SchedulerThreadPool()两个方法同时打印了Begin schedule...信息,是并发定时。

@Async实现定时并发

除了上述这种实现SchedulingConfigurer类来实现定时任务的并发,还可以通过@EnableAsync@Async注解实现定时任务的并发。

package com.example.andya.demo.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.LocalDateTime;

/**
 * @author Andya
 * @create 2020-04-20 22:50
 */
@Configuration
@EnableScheduling
@EnableAsync
public class StaticScheduleTask {

    private Logger LOG = LoggerFactory.getLogger(StaticScheduleTask.class);

    @Async
    @Scheduled(cron = "${schedule.cron}")
    public void firstTask() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            LOG.info("first task time: " + LocalDateTime.now() + "\r\nthread:" + Thread.currentThread().getName());
        }
        Thread.sleep(1000 * 10);
    }

    @Async
//    @Scheduled(fixedDelay = 5000)
    @Scheduled(cron = "0 * * * * *")
    public void secondTask() throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            LOG.info("second task time: " + LocalDateTime.now() + "\r\nthread:" + Thread.currentThread().getName());
        }
        Thread.sleep(1000 * 10);
    }
}


运行结果:


2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
Spring Boot中,我们可以使用@Async注解来异步执行方法。但是默认情况下,Spring Boot使用的是SimpleAsyncTaskExecutor,它是一个单线程的线程池,不能满足高并发场景的需求。因此,我们需要自定义线程池来满足不同的需求。 下面是自定义线程池的步骤: 1. 创建自定义线程池的配置类 ```java @Configuration public class ThreadPoolConfig { @Bean("myThreadPool") public Executor myThreadPool(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 线程池核心线程数 executor.setCorePoolSize(10); // 线程池最大线程数 executor.setMaxPoolSize(20); // 队列容量 executor.setQueueCapacity(100); // 线程池维护线程所允许的空闲时间 executor.setKeepAliveSeconds(60); // 线程池的前缀名 executor.setThreadNamePrefix("myThreadPool-"); // 拒绝策略,当线程池满了并且队列也满了,新任务会被拒绝 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化线程池 executor.initialize(); return executor; } } ``` 2. 在需要异步执行的方法上添加@Async注解,并指定使用的线程池 ```java @Service public class MyService { @Async("myThreadPool") public void asyncMethod(){ // 异步执行的方法体 } } ``` 3. 测试异步执行方法的效果 ```java @RestController public class MyController { @Autowired private MyService myService; @GetMapping("/async") public String async(){ myService.asyncMethod(); return "异步执行中..."; } } ``` 通过以上步骤,我们就可以自定义线程池来异步执行方法了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值