线程池中的队列是线程池的重要组成部分,用于存放待执行的任务。在Java中,线程池常用的队列类型包括无界队列、有界队列、直接提交队列(SynchronousQueue)、优先级队列等。
以下我们来一起简单了解一下吧:
一、队列类型
1、无界队列
- 代表实现:LinkedBlockingQueue(当不指定容量或指定容量为Integer.MAX_VALUE时)
- 特点:队列容量理论上是无限的,但受限于JVM的内存。当内存耗尽时,会抛出OutOfMemoryError。
- 适用场景:适用于任务量非常大,且任务执行时间较长,生产者生成任务的速度不会超过消费者处理任务的速度的场景。但需注意内存溢出的风险。
//构造函数:
new LinkedBlockingQueue<Runnable>()
//允许的任务等待队列的最大长度为:Integer.MAX_VALUE,即能无限的接收新的任务,任何的拒绝策略也差不多没有意义了。
//另外,maximumPoolSize这个参数也没有意义了,因为只有同时满足 核心线程数量够了 + 任务队列workQueue满了 + 现有的线程数<maximumPoolSize最大线//程数,才会去创建额外的线程
//好处: 是LinkedBlockingQueue在应对突然暴增的请求时,它不会抛异常拒绝
//缺点: 是任务堆积过度没有及时处理的话,容易导致内存溢出
2、有界队列
- 代表实现:ArrayBlockingQueue、LinkedBlockingQueue(指定具体容量)
- 特点:队列有一个固定的容量限制,当队列满时,尝试添加新任务的操作会被阻塞,直到队列中有空间可用。
- 适用场景:适用于需要控制任务数量,防止资源耗尽的场景。通过调整队列大小和线程池大小,可以灵活控制任务的并发执行。
new LinkedBlockingQueue(int capacity)://固定容量的阻塞队列
new ArrayBlockingQueue<Integer>(int capacity,true); //其中true是公平锁,只能FIFO排队一 一执行;false允许任务插队,会存在晚来的任务先执行的情况
我们的任务基本的执行顺序基本也是先进先出,直接用了new LinkedBlockingQueue(int capacity),把容量设置得大一点,那样就不会轻易的填满队列导致频繁地创建额外的线程,减少线程频繁切换.
3、直接提交队列(SynchronousQueue)
- 特点:这种队列实际上并不存储任何元素,每个插入操作必须等待另一个线程的相应删除操作(也就是:要添加新任务必须得有空闲的线程才能添加),反之亦然。即一个线程尝试向队列中添加元素时,必须有另一个线程正在等待接收这个元素。
- 适用场景:适用于任务处理时间较短,且生产者和消费者速度大致匹配的场景。它可以有效减少任务在队列中的等待时间,提高系统的响应速度。
new SynchronousQueue(): //队列长度为0,要添加新任务必须得有空闲的线程才能添加,因此要求 maximumPoolSize尽可能的大,还得 配置拒绝策略
4、优先级队列
- 代表实现:PriorityBlockingQueue
- 特点:队列中的元素会根据其优先级进行排序,优先级高的元素会先被取出执行。
- 适用场景:适用于需要按照任务优先级顺序执行的场景。通过调整任务的优先级,可以确保重要任务得到优先处理。
PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)://默认会创建长度为11的优先级队列,第二个参数comparator会按照我们指定的方式进行排序
二、选择建议
在选择线程池中的队列时,需要根据具体的应用场景和需求来决定:
- 任务类型和特点:如果任务处理时间较长,且任务量不确定,可以选择无界队列;如果任务量较大且需要控制并发数,可以选择有界队列。
- 系统资源:考虑系统的内存和CPU资源。无界队列虽然可以处理大量任务,但存在内存溢出的风险;有界队列可以避免内存溢出,但需要合理设置队列大小和线程池大小。
- 性能要求:如果要求系统响应速度快,且任务处理时间较短,可以选择直接提交队列或优先级队列。
- 任务优先级:如果任务有明确的优先级要求,可以选择优先级队列。
线程池中的队列选择需要根据实际情况进行权衡和决策。在设计和配置线程池时,应充分考虑任务类型、系统资源和性能要求等因素。