https://www.jianshu.com/p/cc2281b1a6bc
https://blog.csdn.net/tonywu1992/article/details/83419448
继承关系图
/** * 节点类,用于存储数据 */ 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; }