https://www.jianshu.com/p/cc2281b1a6bc
继承关系图
/**
* 节点类,用于存储数据
*/
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
/**
* 阻塞队列的大小,默认为Integer.MAX_VALUE
*/
private final int capacity;
/**
* 当前阻塞队列中的元素个数
*/
private final AtomicInteger count = new AtomicInteger();
/**
* 阻塞队列的头结点
*/
transient Node<E> head;
/**
* 阻塞队列的尾节点
*/
private transient Node<E> last;
/**
* 获取并移除元素时使用的锁,如take, poll, etc
*/
private final ReentrantLock takeLock = new ReentrantLock();
/**
* notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程
*/
private final Condition notEmpty = takeLock.newCondition();
/**
* 添加元素时使用的锁如 put, offer, etc
*/
private final ReentrantLock putLock = new ReentrantLock();
/**
* notFull条件对象,当队列数据已满时用于挂起执行添加的线程
*/
private final Condition notFull = putLock.newCondition();
这里如果不指定队列的容量大小,也就是使用默认的Integer.MAX_VALUE,如果存在添加速度大于删除速度时候,有可能会内存溢出,这点在使用前希望慎重考虑。
另外,LinkedBlockingQueue对每一个lock锁都提供了一个Condition用来挂起和唤醒其他线程。
默认的构造函数和最后一个构造函数创建的队列大小都为Integer.MAX_VALUE,只有第二个构造函数用户可以指定队列的大小。第二个构造函数最后初始化了last和head节点,让它们都指向了一个元素为null的节点。
最后一个构造函数使用了putLock来进行加锁,但是这里并不是为了多线程的竞争而加锁,只是为了放入的元素能立即对其他线程可见。
put方法来看,它总共做了以下情况的考虑:
队列已满,阻塞等待。
队列未满,创建一个node节点放入队列中,如果放完以后队列还有剩余空间,继续唤醒下一个添加线程进行添加。如果放之前队列中没有元素,放完以后要唤醒消费线程进行消费。
链表算法
private E dequeue() {
// 获取到head节点
Node<E> h = head;
// 获取到head节点指向的下一个节点,也就是节点A
Node<E> first = h.next;
// 获取到下下个节点,也就是节点B
Node<E> next = first.next;
// head的next指向下下个节点,也就是图中的B节点
h.next = next;
// 得到节点A的值
E x = first.item;
first.item = null; // help GC
first.next = first; // help GC
return x;
}