CPU有限,一个CPU只能执行一个线程,在有限的资源下,大量的线程都在等待。通过线程池去管理、创建线程。
一、线程池的核心参数(七个)
救急线程 = 非核心线程 = 临时线程
流程:当任务提交过来之后,先去判断核心线程是否已满,没满则直接通过核心线程去完成
当核心线程满了,去看阻塞队列,阻塞队列没满,则直接放进去,如果阻塞队列满了,则去判断当前线程数是否小于最大线程数(判断有没有临时线程),是的话(有的话)则创建临时线程去执行,没有的话直接触发拒绝策略。
当然如果是按照线程1,2,3提交,当1,2在阻塞队列的时候,3被临时线程去执行,就有3在1,2之前去完成。
同时,每当核心线程或临时线程执行完任务都会去检查阻塞队列是否有需要执行的线程,如果有则使用核心或非核心线程去执行。
如何确定核心线程数?
IO密集型(读取文件多,文件读写、DB读写、网络请求):2*最大并行数 + 1
CPU密集型(计算多,Json转换):最大并行数+1
【计算型的任务需要占用大量的CPU,需要减少线程数目,减少线程的切换来增加效率】
【加1是候补,防止前边有线程出问题,这个线程就可以代替】
二、线程中有哪些常见的阻塞队列
阻塞队列有界的意思是:在创建队列的时候可以指定大小
LinkedBlockingQueue底层是一个单向链表
在创建LinkedBlockingQueue的时候如果不指定大小,则默认给Integer.MAX,有风险,所以要自己设值
两把锁和一把锁的区别就是:
链表可以在两头同时操作数据,因为是两把不一样的锁,而数组只能同时操作一个地方,因为大家公用一把锁。
三、线程池的种类
JUC包下的Executors类提供了四种常见的创建连接池的静态方法
1.创建使用固定线程数的线程池
核心线程数 = 最大线程数 , 阻塞队列没有给初始容量【默认为Integer的最大值】
适用于任务量【需要几个线程】已知,可以控制线程最大并发数,超出的线程在队列中等待
通过内部类重写run方法编写一个子线程任务,创建一个线程池,通过submit去提交任务
2.单线程化的线程池
核心线程数和最大线程数都是1 , 阻塞队列容量也是默认的
适用于按照指定顺序执行的任务
3.可缓存线程池
没有核心线程数,最大线程数为Integer的最大值,且阻塞队列不存储任何元素,来了新线程之后,判断是否有临时线程存活,有的话直接用,没有的话则创建临时线程去执行
适用于任务数比较密集,但是每个任务的执行时间比较短【过长的话,就可能会一直创建临时线程….占用内存】
这种结构设计目的可能是为了在无任务是快速回收线程,有任务时可以使用临时线程数大量执行任务
4.提供了“延迟”和“周期执行”功能的线程池
通过schedule方法提交任务到线程池中
execute提交一个没有返回值的线程,submit有返回值,用future类型接受
四 、为什么不推荐用Executors去创建线程池
Executors类下的SingleThreadPool和FixedThreadExecutors中它们的阻塞队列大小为默认【Integer的最大值】,
可能会堆积大量的请求,从而OOM
而CacheThreadPool中的最大线程数为默认【Integer的最大值】,
可能会造成大量的线程,从而OOM
一般推荐自定义创建线程池【通过new ThreadPoolExecutor,自己往里面填写七个参数】
五、线程池使用的场景
异步调用【主线程中获取【从线程池中】一个新的线程去调用】
为了避免下一级方法影响上一级方法(性能考虑),可使用异步线程调用下一级方法【当然前提是上一级方法不依赖下一级方法的返回值】,这样可以提升方法响应时间
用SpringBoot框架中的@Async注解实现异步调用【在启动类上也要标注@EnableAsync】
项目中一般都是将线程池额外配置成一个Config类,配置成Bean,并给了一个名称
在需要被异步调用的逻辑上加@Async注解【同时标注用的哪个线程池】