springboot篇章整体栏目:
【二】springboot整合swagger(自定义)(超详细)
【四】springboot整合mybatis-plus(超详细)(上)
【五】springboot整合mybatis-plus(超详细)(下)
【十】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的基础上封装的。
接下来我会逐步增加代码进行讲解并实操。
下面展示一下我的目录结构:
框出来的部分是修改的部分以及新增的部分(相比上一章)。
第一步:新增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里面,如下:
解读:此前我已经整合了swagger(最前面的章节),所以可以方便测试,若没有整合可以使用测试工具或者postman之类的。
注意:使用Async注解,里面的参数是指定使用ThreadExecutorConfig里面哪一个方法创建线程池的,此处指定的是上面配置类里面新建的threadPoolTaskExecutor方法。
此处为了测试线程,每个线程存活时间弄长点,所以弄了一个睡眠。
Thread.currentThread().getName()可以获取到线程的名称,此处配置的threadPoolTaskExecutor方法,所以threadPoolTaskExecutor里面设置的线程前缀,此处获取的线程名称前面会加上这个线程前缀。
第三步:演示第一种线程池的效果
打开swagger的网址,获取token,带着请求请求线程池测试接口,如下,多次点击:
可以看到每次请求都产生了一个新的线程,但是线程的数量最多为10,(在配置文件里面配置了最大线程数量)。并且请求很快(异步)。
测试不使用线程池(在controller里面新增方法,不使用线程池),
多次请求该接口:
可以看到创建了大量线程,有多少请求就创建了多少线程,对jvm是一个很大的压力。所以线程池的作用就出来了。
此处再顺便测试一下不开启异步:
如下图:
删除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;
}
使用方式还是同上面一样,如下图:
在测试接口上加上Async注解,参数为刚才新增的创建线程池方法的名称,myThreadPool。
第五步:演示第二种线程池使用
还是通过swagger进行请求,如下图:
可以看到两种线程池使用效果一样。只是写法不同,一个是使用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使用线程池_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