首先,我们来看一下LinkedBlockingQueue的类图。在LinkedBlockingQueue的内部定义了两把锁,分别是takeLock和putLock,其都是ReentrantLock。其插入和取出元素分别在链表两端进行。
从类图中可以看出,LinkedBlockingQueue继承了AbstractQueue类,同时实现了BlockingQueue接口。这个和ArrayBlockingQueue一致。下面我们来看看其中的核心实现及源码。
1、构造方法
构造函数有两个,分别是无参构造函数和有参数构造函数。其中无参构造函数调用的还是这个有参构造函数,传入的参数为Integer.Max_Value。下面来看看有参构造函数。一般LinkedBlockingQueue是无界队列(因为链表节点可以动态增加,不像数组初始化定死了容量)。从这这里设置的最大容量来看,LinkedBlockingQueue也算有界队列。
2、offer操作
offer操作是入队一个元素,在LinkedBlockingQueue中定义了两个哨兵节点,分别是head和tail。添加元素的时候就从tail往后加。取出元素的时候是从head节点开始的。(注意:head节点实际上不存储元素,队首其实是head.next)
从源码的角度来看,首先判断当前元素个数是否达到设定容量。如果是,则直接返回false。否则,会获取putLock进行加锁,然后在元素head后面添加元素,最后释放锁。这里注意两点,第一,在获取锁后再次进行判断当前元素个数是否小于capacity,是因为在获取锁之前,可能有其他线程进行了offer操作。第二,元素入队之后 判断元素+1小于capacity,然后唤醒notFull队列,这是因为有可能有线程在队列满了然后执行put操作而阻塞挂起。
3、put操作
put操作也是向队列中添加一个元素。源码如下:
put操作的首先会检查压入元素是否为空,不为空则继续进行。然后获取锁,如果当前队列已经达到设定的capacity,那么就阻塞自己挂起当前线程等待唤醒。如果队列没有满,则进行入队操作。最后还是要看看唤醒notFull阻塞的线程。最后,释放锁。另外,put操作是可以响应中断的。
3、poll操作
poll操作是从队列取走一个元素,这里是从队列的head节点下一个节点开始取元素。源码如下:
从源码可以看出,poll操作首先会检查队列是否为空,如果为空则直接返回null。否则,获取锁,然后再次判断元素个数是否大于0,这里是二次判断,防止在上面获取锁之前有线程抢断本线程,执行了poll操作取走元素造成队列为空。最后,判断c==capacity,说明取走了一个元素,当前队列最少空一个位置。那么就可以唤醒因为执行put方法被阻塞(该线程因队列满而被挂起)的线程。注意,poll方法是非阻塞的。
4、take操作
take的操作也是从队列首取走一个元素,其源码如下:
从源码可以看出,首先获取队列元素个数,然后加锁。当线程元素为0,那么就无法取走元素,需要阻塞并挂起当前线程(直到put或poll方法执行,队列中有元素了,该线程才被唤醒)。最后,还是判断c==capacity,这里c是线程取走元素之前的长度(count调用的是getAndDecrement方法,先返回当前值然后减1)。注意,此方法也是响应中断的,也就是可以被中断的。
5、peek操作
peek操作是查看在队列头部元素而不删除。源码如下所示:
从源码上可以看出,peek操作也是非阻塞式的,这里head节点是哨兵节点,真正的头节点是head的下一个节点。如果队列头部元素为null,则返回null。否则返回队首的元素值。
6、size操作
由于定义count为AtomicInteger类型,因此返回的size是准确的。源码如下所示: