分享

【十二】springboot整合线程池解决高并发(超详细,保你理解)...

 极风狼 2022-01-17

介绍:接下来我会把学习阶段学到的框架等知识点进行整合,每一次整合是在前一章的基础上进行的,所以后面的整合不会重复放前面的代码。每次的demo我放在结尾,本次是接着上一章的内容延续的,只增加新增的或者修改的代码。

上一章整合了quartz实现定时任务优化,本章是本小白对不熟悉的线程池的边学习边整理的一份学习心得。

先整理一下为什么使用线程池:

在一个网页或者应用程序中,每次请求都需要创建新的线程去处理,所以频繁的创建处理这些请求的线程非常消耗资源,为每个请求创建新线程将花费更多的时间,在创建和销毁线程时花费更多的系统资源。因此同时创建太多线程的 JVM 可能会导致系统内存不足,这就需要限制要创建的线程数,也就是需要使用到线程池。

使用线程池后的改变,线程池的作用:

1、主线程提交新任务到线程池。
2、线程池判断当前线程池的线程数和核心线程数的大小,小于就新建线程处理请求;否则继续判断当前工作队列是否已满。
3、如果当前工作队列未满就将任务放到工作队列中;否则继续判断当前线程池的线程数和最大线程数的大小。
4、如果当前线程池的线程数小于最大线程数就新建线程处理请求,否则就调用RejectedExecutionHandler来做拒绝处理。

整合ThreadPoolTaskExecutor来创建线程,关于ThreadPoolTaskExecutor类:

ThreadPoolTaskExecutor就是在java中ThreadPoolExecutor的基础上封装的。

接下来我会逐步增加代码进行讲解并实操。

下面展示一下我的目录结构:

 

框出来的部分是修改的部分以及新增的部分(相比上一章)。

第一步:新增threadPoolTaskExecutor类,用于创建线程池

  1. /**
  2. * 线程池配置
  3. */
  4. @Configuration
  5. @EnableAsync//开启异步调用
  6. public class ThreadExecutorConfig {

  7. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  8. /** 核心线程数 */
  9. private int corePoolSize = 10;
  10. /** 最大线程数 */
  11. private int maxPoolSize = 10;
  12. /** 队列数 */
  13. private int queueCapacity = 10;

  14. @Bean
  15. public Executor threadPoolTaskExecutor(){
  16. logger.info("创建线程池");
  17. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  18. //设置核心线程数
  19. executor.setCorePoolSize(corePoolSize);
  20. //设置最大线程数
  21. executor.setMaxPoolSize(maxPoolSize);
  22. //设置队列数
  23. executor.setQueueCapacity(queueCapacity);
  24. //设置线程名称前缀
  25. executor.setThreadNamePrefix("threadPoolTaskExecutor-》》》");

  26. // rejection-policy:当pool已经达到max size的时候,如何处理新任务
  27. // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
  28. // 设置拒绝策略.当工作队列已满,线程数为最大线程数的时候,接收新任务抛出RejectedExecutionException异常
  29. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
  30. // 执行初始化
  31. executor.initialize();
  32. return executor;
  33. }
  34. }

解读:注意加上Configuration注解,启动spring时,将该类注册以配置文件形式到spring容器中,EnableAsync注解,开启异步调用。

新建一个threadPoolTaskExecutor方法,加上bean注解,注入spring容器。在方法里面创建一个ThreadPoolTaskExecutor实例,并设置属性值,设置满线程后的策略,最后初始化,并返回这个ThreadPoolTaskExecutor实例。这是一个最基础的线程池创建方法,后面会讲解其他方式。

第二步:使用线程池 

此处我为了简便,直接将业务代码写在了controller里面,如下:

解读:此前我已经整合了swagger(最前面的章节),所以可以方便测试,若没有整合可以使用测试工具或者postman之类的。

注意:使用Async注解,里面的参数是指定使用ThreadExecutorConfig里面哪一个方法创建线程池的,此处指定的是上面配置类里面新建的threadPoolTaskExecutor方法。

此处为了测试线程,每个线程存活时间弄长点,所以弄了一个睡眠。

Thread.currentThread().getName()可以获取到线程的名称,此处配置的threadPoolTaskExecutor方法,所以threadPoolTaskExecutor里面设置的线程前缀,此处获取的线程名称前面会加上这个线程前缀。

第三步:演示第一种线程池的效果

打开swagger的网址,获取token,带着请求请求线程池测试接口,如下,多次点击:

 

可以看到每次请求都产生了一个新的线程,但是线程的数量最多为10,(在配置文件里面配置了最大线程数量)。并且请求很快(异步)。

测试不使用线程池(在controller里面新增方法,不使用线程池),

 多次请求该接口:

可以看到创建了大量线程,有多少请求就创建了多少线程,对jvm是一个很大的压力。所以线程池的作用就出来了。

此处再顺便测试一下不开启异步:

如下图:

删除Async注解,再次请求该接口,可以发现,每次请求执行完毕才会执行下一次请求,响应很慢。

ps:其实线程池的创建和使用到这里就完了,下面介绍另外两种创建方式。

第四步:新建另一种线程池创建

在线程池配置类中新增一个bean,如下:

  1. @Bean
  2. public Executor myThreadPool() {
  3. // 设置核心线程数
  4. int corePoolSize = 5;
  5. // 设置最大线程数
  6. int maxPoolSize = 5;
  7. // 设置工作队列大小
  8. int queueCapacity = 2000;
  9. // 最大存活时间
  10. long keepAliveTime = 30;
  11. // 设置线程名称前缀
  12. String threadNamePrefix = "myThreadPool-->";
  13. // 设置自定义拒绝策略.当工作队列已满,线程数为最大线程数的时候,接收新任务抛出RejectedExecutionException异常
  14. RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
  15. @Override
  16. public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
  17. throw new RejectedExecutionException("自定义的RejectedExecutionHandler");
  18. }
  19. };
  20. // 自定义线程工厂
  21. ThreadFactory threadFactory = new ThreadFactory() {
  22. private int i = 1;

  23. @Override
  24. public Thread newThread(Runnable r) {
  25. Thread thread = new Thread(r);
  26. thread.setName(threadNamePrefix + i);
  27. i++;
  28. return thread;
  29. }
  30. };
  31. // 初始化线程池
  32. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
  33. keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(queueCapacity),
  34. threadFactory, rejectedExecutionHandler);
  35. return threadPoolExecutor;
  36. }

使用方式还是同上面一样,如下图:

在测试接口上加上Async注解,参数为刚才新增的创建线程池方法的名称,myThreadPool。

第五步:演示第二种线程池使用

还是通过swagger进行请求,如下图:

可以看到两种线程池使用效果一样。只是写法不同,一个是使用ThreadPoolTaskExecutor创建的,一个是使用ThreadPoolExecutor创建的。

第六步:开启线程池的当前状态

新增一个配置类,如下:

  1. public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

  2. Logger logger = LoggerFactory.getLogger(MyThreadPoolTaskExecutor.class);

  3. @Override
  4. public void execute(Runnable task) {
  5. logThreadPoolStatus();
  6. super.execute(task);
  7. }

  8. @Override
  9. public void execute(Runnable task, long startTimeout) {
  10. logThreadPoolStatus();
  11. super.execute(task, startTimeout);
  12. }

  13. @Override
  14. public Future<?> submit(Runnable task) {
  15. logThreadPoolStatus();
  16. return super.submit(task);
  17. }

  18. @Override
  19. public <T> Future<T> submit(Callable<T> task) {
  20. logThreadPoolStatus();
  21. return super.submit(task);
  22. }

  23. @Override
  24. public ListenableFuture<?> submitListenable(Runnable task) {
  25. logThreadPoolStatus();
  26. return super.submitListenable(task);
  27. }

  28. @Override
  29. public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
  30. logThreadPoolStatus();
  31. return super.submitListenable(task);
  32. }

  33. /**
  34. * 在线程池运行的时候输出线程池的基本信息
  35. */
  36. private void logThreadPoolStatus() {
  37. logger.info("核心线程数:{}, 最大线程数:{}, 当前线程数: {}, 活跃的线程数: {}",
  38. getCorePoolSize(), getMaxPoolSize(), getPoolSize(), getActiveCount());
  39. }
  40. }

在线程池的配置类里面再写一个bean(第三种创建方式,前面已经加了两个了),如下:

  1. @Bean
  2. public Executor myThreadPoolTaskExecutor() {
  3. ThreadPoolTaskExecutor threadPoolTaskExecutor = new MyThreadPoolTaskExecutor();
  4. // 设置核心线程数
  5. threadPoolTaskExecutor.setCorePoolSize(5);
  6. // 设置最大线程数
  7. threadPoolTaskExecutor.setMaxPoolSize(5);
  8. // 设置工作队列大小
  9. threadPoolTaskExecutor.setQueueCapacity(2000);
  10. // 设置线程名称前缀
  11. threadPoolTaskExecutor.setThreadNamePrefix("myThreadPoolTaskExecutor-->");
  12. // 设置拒绝策略.当工作队列已满,线程数为最大线程数的时候,接收新任务抛出RejectedExecutionException异常
  13. threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
  14. // 初始化线程池
  15. threadPoolTaskExecutor.initialize();
  16. return threadPoolTaskExecutor;
  17. }

解释:此处和上面不同的是,此处实例的是刚才新建的那个ThreadPoolTaskExecutor,通过刚才那个类来创建线程池。使用方式还是跟上面两种一样,通过Async注解写对应的方法名为参数。

使用:

第七步:演示开启线程池的当前状态的效果

 快速请求该接口,效果如下:

我是从这位大佬的文章中学习的,链接在此:SpringBoot使用线程池_Cain的博客-CSDN博客

为了描述简便,其中加入了一些我个人的理解,可能有误。

本期整合到此完毕,接下来会继续更新加强整合,尽情期待。

访问地址:http://localhost:8087/swagger-ui.html或者http://localhost:8087/doc.html

demo地址:studydemo/整合swagger at main · zrc11/studydemo · GitHub

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多