目录
3. 有界阻塞队列——ArrayBlockingQueue和LinkedBlockingQueue
4. 优先无界阻塞队列——PriorityBlockingQueue
BlockingQueue是java.util.concurrent包中定义的一个阻塞队列的接口,concurrent包中实现了多种阻塞队列,它们都实现了该接口。有了这些阻塞队列,可以更好的实现多线程之间的数据共享;而如果能灵活运用这些类,便可以在工作中更好的实现高质量的多线程高并发程序。本章首先从BlockingQueue接口开始,详细分析了concurrent包中的这些阻塞队列的实现。
1. BlockingQueue接口
BlockingQueue接口继承了Queue接口,所以其本身也可以作为一般的队列使用。在此基础上,BlockingQueue定义了如下几个阻塞方法:
-
void put(E e) throws InterruptedException; 该方法为阻塞式入队方法,如果发生中断异常会抛出。
-
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; 该方法为阻塞式入队方法,该方法可以设置一个超时时间,当发生超时时可返回false表示入队失败。
-
E take() throws InterruptedException; 该方法为阻塞式出队方法,如果发生中断异常会抛出。
-
E poll(long timeout, TimeUnit unit) throws InterruptedException; 该方法为阻塞式出对方法,该方法也可以设置一个超时时间,如果发生超时,将会返回null。
2. BlockingQueue的分类
concurrent包中定义了多种阻塞队列:
- ArrayBlockingQueue:有界阻塞队列,通过数组实现,在创建对象时必须指定该队列的容量。
- LinkedBlockingQueue:有界阻塞队列,通过链表实现,在创建对象时可以指定该队列的容量,如果不指定容量默认为Integer类型的最大值。
- PriorityBlockingQueue:无界阻塞队列,可指定初始容量,可指定队列的优先级。
- SynchronousQueue:同步阻塞队列,该阻塞队列比较特殊,其不存储元素,如果该队列中有一个元素,则其他线程不能再往里面插入元素;只有等消费者线程消费之后才能继续插入元素,反之也是一样。
- DelayQueue:延时阻塞队列,该队列可设置延时,入队的元素只有在延时期满之后才能消费。
- LinkedTransferQueue:无界阻塞队列,通过链表实现,相比其他队列,该队列多了两个方法transfer()和tryTransfer()。
- LinkedBlockingDeque:双向阻塞队列,该队列也是通过链表实现,多线程并发时可将锁竞争缩减一半。
本节将concurrent包中所有的阻塞队列的特点进行了简要的概括,下文将详细分析每种阻塞队列的实现原理与使用场景等。
3. 有界阻塞队列——ArrayBlockingQueue和LinkedBlockingQueue
concurrent包实现了两种有界阻塞队列:ArrayBlockingQueue和LinkedBlockingQueue,顾名思义,ArrayBlockingQueue通过数组实现阻塞队列,LinkedBlockingQueue则通过链表实现阻塞队列。两者的实现方案类似,因此,在此以ArrayBlockingQueue为例,介绍有界阻塞队列的实现原理。
在ArrayBlockingQueue中定义了两个方法用来为阻塞队列插入和删除元素。
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
其中,enqueue方法为队列添加元素,dequeue方法为队列删除元素。从这两个方法可以看出,ArrayBlockingQueue中定义了一个items数组用来保存阻塞队列的元素,然后分别定义了putIndex和takeIndex来表示插入和删除元素的位置。当putIndex或takeIndex增长为数组的长度时,会将其置零,表示该队列为一个循环队列。ArrayBlockingQueue中的元素个数则用count来统计。ArrayBlockingQueue可通过构造方法指定阻塞队列的容量capacity。
ArrayBlockingQueue基于ReentrantLock和Condition来实现阻塞队列和线程安全。ArrayBlockingQueue定义了一个ReentrantLock对象lock和两个Condition对象notEmpty和notFull。首先,在调用阻塞队列相关的方法时,通过ReentrantLock为操作加锁;然后进行入队和出队操作。入队时,如果队列已满,对于阻塞类的入队方法会调用notFull的await等待,直到有线程调用出队方法,此时会触发notFull的signal方法通知阻塞的线程入队。反之,如果队列为空,对于阻塞类的出队方法会调用notEmpty的await方法等待,直到有线程调用入队方法,就会触发notEmpty的signal方法通知阻塞的线程出队。而对于非阻塞类的入队和出对方法,入队和出队则直接返回,不会阻塞。
LinkedBlockingQueue是基于链表的有界阻塞队列,因为都是有界阻塞队列,所以其实现与ArrayBlockingQueue类似,在此不再赘述其实现原理。这里仅总结一下ArrayBlockingQueue和LinkedBlockingQueue的异同点,以便在平时应用中选择更加合适的队列。
ArrayBlockingQueue和LinkedBlockingQueue都是有界阻塞队列,也就是说队列的容量有限制,都是通过构造函数来指定队列的容量。而其不同点主要体现在以下几个方面: