上一篇文章分析了ConcurrentLinkedQueue,实现了无限不定长的无锁并发的队列,线程安全。这篇文章则分析另外一种队列实现,线程安全的阻塞队列(BlockingQueue)。
阻塞队列浅析
阻塞队列与非阻塞队列
之前我们接触的linkedlist是非阻塞队列,LinkedList是双向链表,它实现了Dequeue接口。
使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。
但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。
常用阻塞队列
自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,主要有以下几个:
- ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。
- LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
还有另外两种,PriorityBlockingQueue和DelayQueue,前者会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。而后者是一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
LinkedBlockingQueue源码解析
LinkedBlockingQueue继承关系如下,可以看到该队列实现了阻塞队列的接口,并继承了抽象队列:
下图是BlockingQueue接口的结构:
不同的方法实现效果不同,整理后如下表所示:
内部类Node
接着来看LinkedBlockingQueue的内部类Node,也就是链表的结点,这个很简单:
主要属性
|
|
构造方法
|
|
add/remove/element
这三个方法并不是在LinkedBlockingQueue的类中实现,而是如下图:
是在AbstractQueue抽象类中实现,代码逻辑如下;
真相一目了然,只不过是对offer/poll/peek方法的包装而已,正如图二中所示,offer/poll/peek执行失败直接返回false和null,而add/remove/element取而代之的是抛出三个异常而已。接下来分别看offer和poll方法的实现。
offer/poll/peek
|
|
以下为poll方法的源码,大同小异:
peek方法的实现就更简单了,不再赘述。
put/take
据图二所知,put、take方法是两个具备阻塞功能的方法,具体阻塞和非阻塞的区别已经在文章首部说明,在此不在阐述。put方法源码如下:
take的方法与put方法类似,关键在于阻塞。代码如下:
延迟方法 offer/poll
设定一定的等待时间timeout,超过这个时间前者返回false后者返回null。
延迟poll方法代码类似,如下图所示:
这样LinkedBlockingQueue的源码分析,便可结束,总结几点如下:
- 通过ReentrantLock锁的方式,对于offer/poll/peek操作都需要上锁,在大量并发的情况下,效率较低。
- 自带阻塞的方法便于处理带消费者生产者场景的程序,简化了开发的难度。
- 默认容量限制为:Integer.MAX_VALUE=0x7fffffff=2^31-1。