前言
首先,我们知道创建一个线程 可以直接 使用
new Thread(() ->{}).start();
这种形式来创建,当线程的run 方法执行结束,线程就终止了,线程对象就会被垃圾回收机制(GC)释放
然而在我们 开发工作中,特别是一些大厂,高并发的情况随处可见,就需要很多线程来支撑,在没了解线程池之前,我们创建多个线程是这样的
new Thread(() ->{}).start();
new Thread(() ->{}).start();
new Thread(() ->{}).start();
......
无非就是new多个,你要多少个我给你创造多少个
要知道,在高并发的情况下,这样创建多线程是很消耗性能的,不可取,这就需要对线程进行统一管控,就引入了线程池的概念
线程池是什么
线程池是对多个线程进行统一管理维护,当用户把任务提交给线程池,会先进入线程池的阻塞队列,然后线程池中的多个线程会从线程池中的任务队列获取任务执行
线程怎么创建
用这个 ThreadPoolExecutor 一定要用这个
不管什么 newFixedThreadPool, newScheduledThreadPool ,newSingleThreadExecutor ,newCachedThreadPool ,其底层都是调用了 ThreadPoolExecutor 的构造方法去构建
为什么不要使用他们?(是一定不要,而不是不建议)
这边直接说明原因,他们会 导致OOM
OOM,全称“Out Of Memory”,意思就是“内存耗尽,用完了”,来源于java.lang.OutOfMemoryError。
下面是java官方的介绍
Thrown when the Java Virtual Machine cannot allocate an object because
it is out of memory, and no more memory could be made available by the
garbage collector.
意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
可见OOM的严重性
然而 ThreadPoolExecutor 很好的 避免了资源耗尽的情况,我们可以根据需求来自定义参数
现在我们来看 使用ThreadPoolExecutor 的创建
ExecutorService executorService = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
线程池底层+参数讲解
打开原码,来看看里边的参数
来看看这里边的参数
- corePoolSize: 线程池中核心线程的最大数
- maximumPoolSize:线程池中能拥有的最多线程数
- keepAliveTime: 线程空闲时间
- unit: 时间单位
- workQuenu: 任务队列
- threadFactory 线程工厂
- handle 拒绝策略
怎么理解这里边的参数呢?
这边举个营业厅办理业务的 例子
营业厅就是一个线程池,里面的所有柜台就是线程中拥有最多线程数(maximumPoolSize),在服务的柜台就是 线程池的核心线程数(corePoolSize),营业厅等待的座椅就好比 任务队列 (workQuenu)
在线程池中,还有一个概念叫应急线程数 它的数量是 你设置的总线程数- 核心线程数
应急线程数是当 队列中的任务排满 ,以及所有线程都在处理任务 的情况下,这时候又来一个任务,线程池就会启动应急线程数,当应急线程数也启用完了,队列也满了,再来一个任务,这时候就会触发拒绝策略,也就是说,我这个线程池已经容不下你了 (这是有界的情况)
无界是这个等待队列会一直增长,除非资源耗尽,不然不存在容不下的情况,来了就去队列里边等 (无界)
线程空闲时间:线程池会判断已开启应急线程数的空闲时间,如果在设置的时间都是闲着的,就会将这个线程回收,,一般都是60s,一分钟
时间单位:不用说了,就是空闲时间的单位
注意一点:线程池没有什么 我可以站着等,我不坐下来(不进队列),这跟我们生活中办理业务还是有区别的,在线程池中 任务进来就去队列 (当然也不是绝对,别急,马上来解释)
来细说细说任务队列:
在一个线程池中,它的任务队列有以下几种形式
- 有界任务队列 (ArrayBlockingQuene
有界任务队列,就是我们前面例子创建线程使用的了,使用这个队列的线程池 遵循 先进先出 的规格,也就是说 在当 核心线程数 都 不在空闲状态,进来的任务就进入 队列 ,你先进来,等有线程空闲 你就先执行
- 无界任务队列 ( linkedBlockingQuene)
这也是先进先出,先来先执行 不同于有界是 这个等待队列,他会自己增长,前边介绍应急线程数也提到了
- 直接提交队列 (SynchronousQuene)
这个就不一样了,这个队列没有容量,提交给线程池任务不会被线程池缓存,他就是将新的任务给线程执行,要是没有空闲的线程,就尝试创建应急线程,当达到规定最大值,触发拒绝策略
再通俗一点,没有等待队列这个概念
- 优先任务队列 ( priorityBlockingQuene)
这个就等于有VIP机制,是一个特殊的无界队列,他这个可以根据 任务优先级顺序进行执行
所以,前边说的 ,任务进来并不是绝对进队列的原因就在这
简单使用代码实例
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i <7 ; i++) {
int finalI = i;
executorService.execute(() ->{
System.out.println(Thread.currentThread().getName() + "===>办理"+ finalI +"业务");
});
}
executorService.shutdown();
}
Executors.defaultThreadFactory() :默认工厂创建
new ThreadPoolExecutor.AbortPolicy():这是api给出的默认拒绝策略,抛出异常
策略其实也有很多种形式
【以上就是线程池底层原理解析以及简单使用案例了!!】