赞
踩
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-
- import java.util.concurrent.Executors;
- import java.util.concurrent.LinkedBlockingDeque;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
-
- @Configuration
- public class ThreadPoolConfig {
-
- //设置线程数
- @Bean(name = "ApplicationThreadPoolExecutor")
- public ThreadPoolExecutor threadPoolExecutor() {
- return new ThreadPoolExecutor(
- 5,
- 10,
- 10,
- TimeUnit.SECONDS,
- new LinkedBlockingDeque<>(100),
- Executors.defaultThreadFactory(),
- new ThreadPoolExecutor.DiscardPolicy()
- );
- }
-
- @Bean(name = "ApplicationThreadPoolExecutor")
- public ThreadPoolTaskExecutor taskExecutor() {
- ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
- taskExecutor.setCorePoolSize(5);
- taskExecutor.setMaxPoolSize(10);
- taskExecutor.setQueueCapacity(100);
- taskExecutor.setKeepAliveSeconds(10);
- taskExecutor.setThreadNamePrefix("taskExecutor-");
- taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
- return taskExecutor;
- }
-
- }
其实是各自的包不一样
ThreadPoolExecutor 是在 java.util.concurrent下的,jdk自带的创建线程池方法。
ThreadPoolTaskExecutor 是在springboot封装好的线程池类。ThreadPoolTaskExecutor 也是调用 ThreadPoolExecutor 创建的线程池。
还有就是,springboot 中 ThreadPoolTaskExecutor 创建线程池阻塞队列是:
1、如果设置了阻塞队列容量,则是创建 LinkedBlockingQueue ,链表的有界阻塞队列
2、如果是没有设置阻塞队列容量,则是创建 SynchronousQueue ,一种无缓冲的等待队列(也即只能放入单个元素的队列)
到这里不禁有个疑问,既然springboot中创建线程池默认使用的是LinkedBlockingQueue和SynchronousQueue,那么如果是我们自己用jdk自带的ThreadPoolExecutor创建线程池,阻塞队列也优先考虑用这两个就好了。
springboot为什么不用 ArrayBlockingQueue 而是用 LinkedBlockingQueue 呢?
应该是考虑到并发性能的问题。
LinkedBlockingQueue:基于链表的有界(但大小默认值为 integer.MAX_VALUE)阻塞队列。同 ArrayBlockingQueue 类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回; 只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue 可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,是因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
从上面创建的时候我们可以看到,在@Bean中加了name属性,例如@Bean(value = "myThreadPool")
springboot中开启异步任务需要设置@EnableAsync,之后在异步调用的方法上加上@Async注解即可调用异步任务。但是这个时候异步任务还不能用到我们设置线程池创建的线程,还需要在@Async注解中写上需要调用Bean的值,例如:@Async("myThreadPool")
也是在定时任务调用的方法上面加上@Async("XXXX")即可
- //定时任务
- @Scheduled(fixedRate = 5000)
- @Async("ApplicationThreadPoolExecutor")
- public void ScheduledTest() {
- System.out.println(Thread.currentThread().getName() + "---> " + new Date() + " invoke syncFollowingWxAccount()");
-
- //TODO 具体处理数据业务
- }
-
- //异步任务
- @Async("ApplicationThreadPoolExecutor")
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void syncFromDb() {
-
- //TODO 具体处理数据业务
-
- }
如果不设置线程池,那么 springboot 就会创建一个定时任务线程来执行多个定时任务,有可能会引起定时任务的阻塞。
这样来说,定时任务需要创建一个线程来触发定时任务;定时任务加了异步调用之后,也类似于普通方法的的异步调用。
@Scheduled 只是标识是否定时任务,@Async标识是否异步任务,两个注解加起来就是定时异步任务。
不得不感叹 springboot 多注解的实现。
- public class Index {
-
- //引入线程池配置
- @Autowired
- private ThreadPoolExecutor threadPoolExecutor;
-
- @GetMapping("/async")
- public void async() throws ExecutionException, InterruptedException {
-
- CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
- //具体业务处理
- }, threadPoolExecutor);
- future.get();
- }
-
- }
在用JVisualvm 查看项目线程的时候发现,springboot 在启动的时候默认开启了10 个线程,???我根本没有设置了这个!
了解之后知道这是 springboot 默认启动线程,也可以说是tomcat 启动的默认线程,可以通过下面参数修改:
- server.port=9999
- server.tomcat.uri-encoding=utf-8
-
- #初始化时创建的线程数
- server.tomcat.threads.min-spare=5
-
- #最大并发数,也就是最大线程数
- server.tomcat.threads.max=100
-
- #可以放到处理队列中的请求数
- server.tomcat.accept-count=1000
-
- #接受和处理的最大连接数
- server.tomcat.max-connections=10
-
- #一个请求最多等待时间,单位ms
- server.tomcat.connection-timeout=30000
-
- # session最大超时时间(分钟),默认为30分钟
- server.servlet.session.timeout=30
springboot中,可以配置tomcat的线程池,这个类似于主线程池,会根据访问量对控制主线程。在此之外,我们可以配置异步线程池,目的是为了在不影响tomcat线程的执行效率,提高系统并发量。由于每个线程占用的内存和cpu的,创建线程是有代价的,所以在需要把握好创建线程的量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。