SpringBoot项目中自定义线程池与异步调用案例

SpringBoot项目中自定义线程池与异步调用案例

一、自定义线程池

1、配置文件
server:
  port: 9006
# 线程池配置参数
task:
  pool:
    corePoolSize: 10 # 设置核心线程数
    maxPoolSize: 20  # 设置最大线程数
    keepAliveTime: 300 # 设置空闲线程存活时间(秒)
    queueCapacity: 100 # 设置队列容量
    threadNamePrefix: "gblfy-signpolicy-asynnotify-" # 设置线程名称前缀
    awaitTerminationSeconds: 60 #  设置线程池等待终止时间(秒)
spring:
  main:
    allow-bean-definition-overriding: true
2、线程池配置属性对应的实体类映射
package com.panda.threadpool.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @Description: 线程池配置属性映射实体类
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-28  16:58
 */
@Data
@ConfigurationProperties(prefix = "task.pool")
public class ThreadPoolProperties {

    private Integer corePoolSize;

    private Integer maxPoolSize;

    private Integer keepAliveTime;

    private Integer queueCapacity;

    private Integer awaitTerminationSeconds;

    private String threadNamePrefix;
}

3、开启异步线程支持
package com.panda.threadpool;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.panda.threadpool.config.ThreadPoolProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * @Description:
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-28  16:55
 */
// 排除数据源和数据库连接池配置 (此项目中引入依赖后没有用到所以要排除)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class}) 
@EnableAsync // 开启异步线程支持
@EnableConfigurationProperties({ThreadPoolProperties.class}) // 开启配置属性支持
public class ApplicationThreadPool {
    public static void main(String[]args){
        SpringApplication.run(ApplicationThreadPool.class, args);
    }
}

4、创建自定义线程池配置类
package com.panda.threadpool.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Description: 创建自定义线程池配置类
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-28  17:12
 */
@Configuration
public class AsyncScheduledTaskConfig {

    private static final Logger logger = LoggerFactory.getLogger(AsyncScheduledTaskConfig.class);

    @Resource
    private ThreadPoolProperties poolProperties;


    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    /**
     *  1.当池子大小小于corePoolSize,就新建线程,并处理请求
     *  2.当池子大小等于corePoolSize,把请求放入workQueue(QueueCapacity)中,
     *	  池子里的空闲线程就去workQueue中取任务并处理
     *  3.当workQueue放不下任务时,就新建线程入池,并处理请求,
     *	  如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
     *  4.当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,
     *    如果无请求可处理就自行销毁
     * @return
     */

    @Bean("customAsyncThreadPool") // 自定线程池在容器中的实例名称
    public Executor customAsyncThreadPool() {

        logger.info("start asyncServiceExecutor");

        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 线程池最大线程数
        threadPoolTaskExecutor.setMaxPoolSize(poolProperties.getMaxPoolSize());
        // 线程池核心线程数
        threadPoolTaskExecutor.setCorePoolSize(poolProperties.getCorePoolSize());
        // 任务队列的大小
        threadPoolTaskExecutor.setQueueCapacity(poolProperties.getQueueCapacity());
        // 线程池名的前缀
        threadPoolTaskExecutor.setThreadNamePrefix(poolProperties.getThreadNamePrefix());
        // 允许线程池的空闲时间30秒
        threadPoolTaskExecutor.setKeepAliveSeconds(poolProperties.getKeepAliveTime());
        // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        threadPoolTaskExecutor.setAwaitTerminationSeconds(poolProperties.getAwaitTerminationSeconds());

        /**
         * 拒绝处理策略
         * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
         * AbortPolicy():直接抛出异常。
         * DiscardPolicy():直接丢弃。
         * DiscardOldestPolicy():丢弃队列中最老的任务。
         */
        /**
         * 特殊说明:
         * 1. 这里演示环境,拒绝策略咱们采用抛出异常
         * 2.真实业务场景会把缓存队列的大小会设置大一些,
         *   如果,提交的任务数量超过最大线程数量或将任务环缓存到本地、redis、mysql中,保证消息不丢失
         * 3.如果项目比较大的话,异步通知种类很多的话,建议采用MQ做异步通知方案
         */
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 线程初始化
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

二、实现使用自定义线程池异步调用第三方接口案例

1、控制层

模拟接口testHello并行调用hello1和hello2接口

package com.panda.threadpool.controller;

import com.alibaba.fastjson.JSONObject;
import com.panda.threadpool.service.ThreadPoolService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.ExecutionException;


/**
 * @Description: 异步调用测试类
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-30  11:58
 */
@RestController
@RequestMapping(value = "/test")
public class TestController {

    @Resource
    private ThreadPoolService threadPoolService;

    private static final Logger logger = LoggerFactory.getLogger(TestController.class);

    /**
     *  模拟远程接口1
     * @param name
     * @param age
     * @return
     * @throws InterruptedException
     */
    @GetMapping(value = "/hello1")
    public JSONObject hello1(String name,Integer age) throws InterruptedException {
        logger.info("this is hello1 ---> sleep(5000) begin " + new Date());
        Thread.sleep(5000);
        logger.info("this is hello1 ---> sleep(5000) end   " + new Date());
        JSONObject res = new JSONObject();
        res.put("name", "hello1-" + name);
        res.put("age","hello1-"+age);
        return res;
    }

    /**
     * 模拟远程接口2
     * @param name
     * @param age
     * @return
     * @throws InterruptedException
     */
    @GetMapping(value = "/hello2")
    public JSONObject hello2(String name,Integer age) throws InterruptedException {
        logger.info("this is hello2 ---> sleep(8000) begin " + new Date());
        Thread.sleep(8000);
        logger.info("this is hello2 ---> sleep(8000) end   " + new Date());
        JSONObject res = new JSONObject();
        res.put("name", "hello2-" + name);
        res.put("age","hello2-" + age);
        return res;
    }

    /**
     *  调用远程接口
     * @param name
     * @param age
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @GetMapping(value = "/testHello")
    public JSONObject testHello(@RequestParam("name") String name,
                                @RequestParam("age") Integer age) throws ExecutionException, InterruptedException {
        return threadPoolService.testHello(name,age);
    }


}
2、定义多线程调用的业务类
package com.panda.threadpool.service.impl;

import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;

/**
 * @Description: 多线程调用业务类
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-30  12:16
 */
@Service
public class OtherService {

    private static final Logger logger = LoggerFactory.getLogger(OtherService.class);

    @Resource
    private RestTemplate restTemplate;

    /**
     * 调用hello1接口
     * @param name
     * @param age
     * @return
     */
    @Async("customAsyncThreadPool") // 使用自定义线程池
    public CompletableFuture<JSONObject> invokeHello1(String name, Integer age) {
        String url = "http://localhost:9006/test/hello1?name=" + name + "&age="+age;
        logger.info("多线程开始执行{}, threadId:{},threadName:{}",
                url,Thread.currentThread().getId(),Thread.currentThread().getName());
        ResponseEntity<JSONObject> responseEntity = restTemplate.getForEntity(url, JSONObject.class);
        JSONObject jsonRes = responseEntity.getBody();
        logger.info("多线程执行完毕{}, 响应Res:{}, threadId:{}, threadName:{}",
                url, jsonRes, Thread.currentThread().getId(), Thread.currentThread().getName());
        return CompletableFuture.completedFuture(jsonRes);
    }

    /**
     * 调用接口hello2
     * @param name
     * @param age
     * @return
     */
    @Async("customAsyncThreadPool") // 使用自定义线程池
    public CompletableFuture<JSONObject> invokeHello2(String name, Integer age) {
        String url = "http://localhost:9006/test/hello2?name=" +name + "&age="+age;
        logger.info("多线程开始执行{},threadId:{},threadName:{}",url,
                Thread.currentThread().getId(),Thread.currentThread().getName());
        ResponseEntity<JSONObject> responseEntity = restTemplate.getForEntity(url, JSONObject.class);
        JSONObject jsonRes = responseEntity.getBody();
        logger.info("多线程执行完毕{},响应Res:{},threadId:{},threadName:{}",
                url,jsonRes,Thread.currentThread().getId(),Thread.currentThread().getName());
        return CompletableFuture.completedFuture(jsonRes);

    }

}

3、定义使用多线程的接口ThreadPoolService
package com.panda.threadpool.service;

import com.alibaba.fastjson.JSONObject;

import java.util.concurrent.ExecutionException;

/**
 * @Description:
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-28  17:33
 */
public interface ThreadPoolService {
    
    JSONObject testHello(String name,Integer age) throws ExecutionException, InterruptedException;
}

4、定义使用多线程的接口实现类ThreadPoolServiceImpl
package com.panda.threadpool.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.panda.threadpool.service.ThreadPoolService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @Description: 自定义线程池异步调用测试
 * @Created: with IntelliJ IDEA.
 * @Author: luzic
 * @CreateTime: 2022-09-28  22:47
 */
@Service
public class ThreadPoolServiceImpl implements ThreadPoolService {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolServiceImpl.class);

    @Resource
    private OtherService otherService;

    @Override
    public JSONObject testHello(String name,Integer age) throws ExecutionException, InterruptedException {
        logger.info("this is threadPoolServiceImpl.testHello({}),begin-> {}",name, new Date());
        // 此处hello1和hello2均为异步调用
        CompletableFuture<JSONObject> hello1Future = otherService.invokeHello1(name,age);
        CompletableFuture<JSONObject> hello2Future = otherService.invokeHello2(name,age);
        // 这里可以处理其他的业务逻辑
        logger.info("async invoke doing...");
        // 开始处理hello1和hello2的接口响应
        CompletableFuture.allOf(hello1Future,hello2Future).join();
        JSONObject hello1Res = hello1Future.get();
        JSONObject hello2Res = hello2Future.get();
        logger.info("this is threadPoolServiceImpl.testHello({}), end -> {}",name,new Date());
        JSONObject jsonRes = new JSONObject();
        jsonRes.put("hello1-name",hello1Res.get("name"));
        jsonRes.put("hello1-age",hello1Res.get("age"));
        jsonRes.put("hello2-name",hello2Res.get("name"));
        jsonRes.put("hello2-age",hello2Res.get("age"));
        return jsonRes;
    }
}

5、接口测试

在浏览器中输入:http://localhost:9006/test/testHello?name=xiaozhang&age=99

以上就是SpringBoot实现自定义线程池,并使用自定义线程池实现异步调用接口

posted @ 2022-09-30 17:29  龙一ξ  阅读(480)  评论(0编辑  收藏  举报