一、什么是阻塞队列
1.1 什么是队列
队列是先进先出。
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
1.2 什么是阻塞队列?
队列已经满的时候,往队列塞数据的时候阻塞
队列为空的时候,往外拿取元素的时候阻塞
1.3 BlockQueue
阻塞方法:
take():E 队列为空的时候,发生阻塞
put(E): 往队列满的时候,往队列插数据发生阻塞
非租塞方法:
二、阻塞队列的使用场景
生产者-消费者模式
2.1 作用
平衡生产者和消费者性能均衡问题。(背压)
2.2 常见的阻塞队列
- ArrayBlockingQueque: 一个由数组结构组成的有界阻塞队列
- LinkedBlockingQueque: 一个由链表组成的有界阻塞队列
- PriorityBlockingQueque: 一个支持优先级排序的无界阻塞队列,放入元素按照优先级排序
- DelayQueue: 一个使用优先级队列组成的无界阻塞队列,放入的元素要支持Delay接口。元素的剩余时间没到的话,拿不到元素。
- SynchronousQueue: 一个不存在元素的阻塞队列,将生产者元素直接交给消费者
- LinkedTransferQueue: 一个由链表组成的无界阻塞队列,往队列中放入元素的话,如果有消费者在等待,直接交给消费者,而不需要放入队列。
- LinkedBlockingDeque: 一个由链表组成的双向阻塞队列
2.3 什么是有界?
有界:队列长度是有限的,满了以后生产者就会阻塞
无界:队列长度是无限的,可以不停的往里添加东西而不会阻塞
三、线程池
3.1 什么是线程池?
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。它由线程池管理器、工作队列和线程池线程组成。
线程池的基本概念是,在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用
3.2 为什么使用线程池?
好处:
- 重用线程
线程池会在内部维护一组可重用的线程,避免了频繁地创建和销毁线程的开销,提高了线程的利用率。 创建线程需要资源,切换上下文开销大。
- 控制并发度
线程池可以限制并发执行的线程数量,防止系统过载。通过调整线程池的大小,可以控制并发度,避免资源消耗过大。
- 提供线程管理和监控
线程池提供了一些管理和监控机制,例如线程池的创建、销毁、线程状态的监控等,方便开发人员进行线程的管理和调试。
- 提供任务队列
线程池通常会使用任务队列来存储待执行的任务,这样可以实现任务的缓冲和调度。
缺点:
1) 需要合理配置:线程池的性能和效果受到配置参数的影响,需要根据具体的应用场景和硬件环境来合理配置线程池的大小、任务队列的大小等参数。
2)可能引发资源泄露:如果线程池中的线程长时间闲置而不被使用,可能会导致资源的浪费和泄露。
3)可能引发死锁:在使用线程池时,如果任务之间存在依赖关系,可能会引发死锁问题,需要额外的注意和处理。
3.3 线程池工作机制
ThreadPoolExcutor
3.3.1 线程池的参数
corePoolSize: 线程池的核心线程数。
maxinumPoolSize: 最大线程数。线程池所能够使用的最大线程数量
keepAliveTime+TimeUnit: 空闲线程的存活时间
workQueue:BlockingQueue: 工作任务的缓存阻塞队列
RejectExecutionHandler: 拒绝策略。超出线程池能力的任务的处理策略。
3.3.2 工作原理
1)用户往线程池提交任务。当前启动的工作线程数小于核心线程数,则创建一个新的工作线程吹任务;
2)如果工作线程数已经大于核心线程数,则会将工作任务放入阻塞队列中。
3)如果阻塞队列已经填满,如果工作线程数小于最大线程数,则新启动一个线程处理工作任务;
4)如果最大线程数也满了,则使用拒绝策略。
3.3.3 四种拒绝策略
DiscardOldestPolicy: 丢弃最老的任务
AbortPolicy: 直接抛出异常。默认
CallerRunsPolicy: 调用者执行任务。
DiscardPolicy: 丢弃最新提交的任务
3.4 提交任务的方式
execute: 提交任务给线程池,不关心返回结果。
submit: 提交任务并且得到返回值
3.5 线程池关闭
shutdown:
shutdownNow:
3.6 合理配置线程池
任务特性:
CPU密集型:大量的CPU运算,核心线程数最好CPU核心数+1
IO密集型:网络通讯、读写IO。核心线程数可以设置 CPU核心数*2
混合型:既有CPU密集型任务,也有IO密集型任务。
“计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。”
参考文献: