springboot项目快速上手多线程开发
先说在项目中如何使用多线程,其次再进行原理说明
一.直接上手项目教程
在springboot项目中首先在启动类中添加@EnableAsync来开启异步的支持,使用@Async能够对某个方法进行异步执行
1.在启动类中添加注解@EnableAsync
2.配置线程池
如下配置线程核心线程数,最大线程数等,
在这里配置,线程池的name为asyncServiceExecutor,在业务代码中会用到
@Configuration
@EnableAsync
public class ThreadConfig {
@Bean(name = "asyncServiceExecutor")
public ThreadPoolTaskExecutor executor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(10);
//配置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(100);
//线程的名称前缀
executor.setThreadNamePrefix("Executor-");
//线程活跃时间(秒)
//executor.setKeepAliveSeconds(60);
//等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置拒绝策略
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
3.在业务代码中书写代码
其中@Async(name = “asyncServiceExecutor” ) 用来明确开启多线程时所使用的线程池配置
4.调用刚刚书写的业务代码
直接这样调用即可完成在springboot项目中多线程的使用
二.原理说明
线程池(Thread Pool)是一种基于池化思想管理线程的工具。
为什么要使用线程池?
- 降低资源消耗:通过池化技术重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
ThreadPoolExecutor 7 大参数:
- corePoolSize
:核心线程数。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的核心线程能够执行新任务也会创建线程,直到需要执行的任务数大于核心线程数时就不再创建。这些线程创建后并不会销毁,而是一种常驻线程。 - maximumPoolSize:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。但线程池内的总线程数不会超过最大线程数,且如果使用了无界任务队列则该参数不起效果。
workQueue:任务队列。用于存放等待执行的任务的阻塞队列。如果核心线程都在执行任务,并且任务队列没有满,则将新任务存储在这个任务队列。
- ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,按 FIFO 排序任务。
- LinkedBlockingQueue: 一个基于链表结构的阻塞队列,可有界也可无界,按 FIFO
排序任务,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()
使用了这个队列。 - SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于
LinkedBlockingQueue。静态工厂方法 Executors.newCachedThreadPool() 使用了这个队列。 - PriorityBlockingQueue:一个具有优先级的无界阻塞队列。
- keepAliveTime:表示核心线程外的线程的空闲存活时间,也就是工作线程空闲后,核心线程外的线程不会立即销毁,而是会等到时间超过keepAliveTime时才会被销毁。
- unit :keepAliveTime 参数的时间单位。
- threadFactory:为线程池提供创建新线程的线程工厂,可以用来设置线程名、是否为守护线程等等。
- handler :拒绝 / 饱和策略。当队列和线程池都满了,则采取饱和策略处理提交的新任务。当 ThreadPoolExector
已经关闭时,execute() 方法会调用 Handler。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。 - AbortPolicy :直接抛出异常 RejectedExecutionException 来拒绝新任务的处理。
- CallerRunsPolicy :调用执行自己的线程运行任务,也就是直接在调用 execute
方法的线程中运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。这种策略会降低对于新任务提交速度,影响程序的整体性能。 - DiscardPolicy :不处理新任务,直接丢弃掉。
- DiscardOldestPolicy : 丢弃队列里头部(最早)的任务,并执行当前任务。
线程池中阻塞队列的作用
- 普通队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前要入队的任务;而阻塞队列可以通过阻塞保留住当前想要继续入队的任务。
- 阻塞队列自带阻塞和唤醒的功能,当任务队列中没有任务时,阻塞队列可以阻塞要获取任务的线程,线程池利用阻塞队列的take方法将线程挂起,让其进入wait状态,让核心线程不占用cpu资源。
线程池工作流程
当向线程池提交一个任务后的处理流程:
- 如果当前运行的线程数小于 corePoolSize,无论线程是否空闲,都会新建一个核心线程来执行任务。(注意,执行这一步需要获得全局锁)。
- 如果当前运行的线程数 >= corePoolSize 时,则将任务加入任务队列。
- 当任务队列已满,则创建新的线程(非核心线程)来处理任务(注意,执行这一步需要获得全局锁)。
- 当任务队列已满, 且当前运行总线程数达到了 maximumPoolSize,则会采取拒绝策略进行处理。