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

导读:本篇文章讲解 【十二】springboot整合线程池解决高并发(超详细,保你理解),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

 springboot篇章整体栏目: 


【一】springboot整合swagger(超详细

【二】springboot整合swagger(自定义)(超详细)

【三】springboot整合token(超详细)

【四】springboot整合mybatis-plus(超详细)(上)

【五】springboot整合mybatis-plus(超详细)(下)

【六】springboot整合自定义全局异常处理

【七】springboot整合redis(超详细)

【八】springboot整合AOP实现日志操作(超详细)

【九】springboot整合定时任务(超详细)

【十】springboot整合redis实现启动服务即将热点数据保存在全局以及redis(超详细)

【十一】springboot整合quartz实现定时任务优化(超详细)

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

【十三】springboot整合异步调用并获取返回值(超详细)

【十四】springboot整合WebService(超详细)

【十五】springboot整合WebService(关于传参数)(超详细)

【十六】springboot整合WebSocket(超详细)

【十七】springboot整合WebSocket实现聊天室(超详细)


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

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

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

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

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

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

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

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

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

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

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

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

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

/**
 * 线程池配置
 */
@Configuration
@EnableAsync//开启异步调用
public class ThreadExecutorConfig {

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

    @Bean
    public Executor threadPoolTaskExecutor(){
        logger.info("创建线程池");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //设置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //设置队列数
        executor.setQueueCapacity(queueCapacity);
        //设置线程名称前缀
        executor.setThreadNamePrefix("threadPoolTaskExecutor-》》》");

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

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

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

第二步:使用线程池 

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

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

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

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

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

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

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

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

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

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

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

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

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

 多次请求该接口:

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

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

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

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

如下图:

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

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

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

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

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

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

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName(threadNamePrefix + i);
                i++;
                return thread;
            }
        };
        // 初始化线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
                keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(queueCapacity),
                threadFactory, rejectedExecutionHandler);
        return threadPoolExecutor;
    }

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

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

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

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

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

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

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

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

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

新增一个配置类,如下:

public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

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

    @Override
    public void execute(Runnable task) {
        logThreadPoolStatus();
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        logThreadPoolStatus();
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        logThreadPoolStatus();
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        logThreadPoolStatus();
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        logThreadPoolStatus();
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        logThreadPoolStatus();
        return super.submitListenable(task);
    }

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

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

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

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

使用:

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

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

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

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

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

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

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

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

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

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

码字不易,若帮到各位,帮忙三连,感谢

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/12803.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!