目录
概述
BlockingQueue阻塞队列接口,是一个支持两个附加操作的队列。
这两个附加操作分别是:
- 在队列为空时,获取元素的线程会等待队列变为非空。
- 当队列元素已满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是往队列里拿出元素的线程。阻塞队列的角色就是充当生产者存放元素的容器,而消费者只从容器里拿出元素,这也在一定程度上平衡了二者之间的处理能力,解决了它们之间的强耦合关系,在保证并发安全的同时,提高了队列的存取效率。
ArrayBlockingQueue
ArrayBlockingQueue,顾名思义,它是基于数组实现的,所以它是一个有界队列,在它的内部维护了一个定长数组,以便缓存队列中的数据对象,按照FIFO的原则排序;
入队与出队的操作,使用同一个ReentrantLock来进行控制。
下面是它的部分源码:
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
/** The queued items */
// ArrayBlockingQueue使用定长数组做为存储结构
final Object[] items;
/** Main lock guarding all access */
final ReentrantLock lock;
// 创建时传入数组容量(长度)
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
}
我们通过它来自定义一个线程池:
ExecutorService executorService =
new ThreadPoolExecutor(4,
10,
2, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
使用ArrayBlockingQueue的意义在于: 通过使用有界队列,让maximumPoolSize最大线程数参数有意义。
原因:
若有新的任务需要执行,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,才会将新的任务加入到工作队列中。若队列已满,也就是超过数组的长度时,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。
在这种情况下,线程池的线程数量上限与有界队列的容量有直接关系。如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,才会创建核心线程之外的非核心线程,这样maximumPoolSize这个参数才有了意义。
LinkedBlockingQueue
LinkedBlockingQueue是一个无界队列,基于单向链表结构,可以选择进行设置容量。如果不设置容量的化话,最大长度为Integer.MAX_VALUE。
入队和出队操作使用不同的ReentrantLock来进行控制,所以LinkedBlockingQueue的吞吐量通常高于ArrayBlockingQueue。
下面是它的部分源代码:
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// 单向链表Node节点
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
// 存取操作使用两把不同的锁来进行管理
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
// 默认按照Integer.MAX_VALUE设置容量
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
}
FixedThreadPool、SingleThreadExecutor线程池都使用LinkedBlockingQueue 队列;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
注意事项
- 如果使用无界队列,根据线程池的执行流程来看,一旦corePoolSize达到最大时,那么将会无限制地添加线程任务到队列中,那么也就意味着maximumPoolSize这个参数是无效的;
- 所以当我们使用这种任务队列模式时,一定要注意任务提交与任务处理之间的协调与控制,在高并发的环境下,会导致大量的线程任务堆积到阻塞队列中而得不到及时的处理。
DelayedWorkQueue
DelayedWorkQueue是基于堆结构的延迟队列,基于数组实现,初始容量为16,leader线程用于获取堆顶元素(队列头部元素)。该队列根据指定的延迟时间从小到大排序,如果延迟时间相同,则根据插入到队列的先后顺序排序。
下面是它的部分源码:
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable> {
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue = new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private Thread leader = null;
}
ScheduledThreadPool线程池使用了这个队列。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//.....
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
PriorityBlockingQueue
PriorityBlockingQueue是一个基于优先级的无界队列(优先级的判断通过构造方法传入Comparator比较器或者通过元素实现Comparable接口来决定)。
注意事项
- PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
- PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
SynchronousQueue
SynchronousQueue是一个同步队列,它是一个不存储元素的阻塞队列(内部没有保存元素的数据结构容器),每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
CachedThreadPool线程池使用了这个队列。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
下面是根据SynchronousQueue 创建的线程池:
public class Main {
public static void main(String[] args) {
// maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略(直接抛出异常)
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 10, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new ThreadPoolExecutor.AbortPolicy());
// 执行的线程任务大于maximumPoolSize,执行拒绝策略
for (int i = 1; i <= 3; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "被执行!");
}
});
}
// 关闭线程池
pool.shutdown();
}
}
当使用SynchronousQueue创建线程池时, 创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。
使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则会根据设置的handler参数执行拒绝策略。
所以CachedThreadPool这个线程池的最大线程数为Integer.MAX_VALUE,就是为了防止线程任务的数量大于最大线程数,线程池执行拒绝策略。
那么在这种情况下,开发者需要对程序的并发量有个准确的预估,才能设置合适的maximumPoolSize数量,否则很容易就会导致线程池执行拒绝策略。
总结
将这五个线程池可以分为:
- 有界队列: ArrayBlockingQueue,DelayedWorkQueue
- 无界队列: LinkedBlockingQueue,PriorityBlockingQueue
- 同步队列: SynchronousQueue