概述
LinkedBlockingQue是一个基于链表实现的可设置容量的无界(最大值Integer.MAX_VALUE)阻塞队列。队头的元素是插入时间最长的,队尾的元素是最新插入的。新的元素将会被插入到队列的尾部。
1、数据结构
基于链表,所以队列中至少有一个空元素,头结点不含元素。
2、原理
LinkedBlockingQueue中有两把锁,takeLock和putLock,即读写各一把锁。这就意味着只有读读、写写互斥,读写可以同时进行。 为了保证读写操作过程中队列元素个数的可见性和原子性,队列的元素变量count类型为AtomicInteger,且只在读写锁定过程中对其值进行增减,后面源码部分会涉及到。
3、 源码解读
3.1 变量
LinkedBlockingQueue阻塞队列中定义了入队锁putLock、出队锁takeLock及其相关的两个Condition对象notFull 、notEmpty ,容量capacity、队头head、队尾last节点、队列中元素数量count。
不再做详细介绍。
3.2 构造方法
LinkedBlockingQueue的构造方法有三个,分别如下:
- 未设置容量大小,默认容量大小为
Integer.MAX_VALUE; - 设置容量大小为
capacity,并且初始化队列。 - 将
Collection集合中的元素导入队列,其实用的就是put(E x)方法,后面将put方法时详述,这里不做详细介绍。
3.3 put(E x)入队方法
用于将元素插入==队列尾部==,队列已满会造成==入队线程阻塞==。先看流程图,然后再看源码。由于是为了让自己加深记忆,所以流程图画的比较细。

public void put(E e) throws InterruptedException {
//不允许元素为null
if (e == null) throw new NullPointerException();
int c = -1;
//以当前元素新建一个节点
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//获得入队的锁
putLock.lockInterruptibly();
try {
//如果队列已满,那么将该线程加入到Condition的等待队列中
while (count.get() == capacity) {
notFull.await();
}
//将节点入队
enqueue(node);
//得到插入之前队列的元素个数
c = count.getAndIncrement();
//如果还可以插入元素,那么释放等待的入队线程
if (c + 1 < capacity)
notFull.signal();
} finally {
//解锁
putLock.unlock();
}
//通知出队线程队列非空
if (c == 0)
signalNotEmpty();
}
3.4 take(E x)出队方法
用于取出==队尾元素==,如果队列为空,会在成==出队线程阻塞==。同样的,想看流程图在看源码。

public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//获取takeLock锁
takeLock.lockInterruptibly();
try {
//如果队列为空,那么加入到notEmpty条件的等待队列中
while (count.get() == 0) {
notEmpty.await();
}
//得到队头元素
x = dequeue();
//得到取走一个元素之前队列的元素个数
c = count.getAndDecrement();
//如果队列中还有数据可取,释放notEmpty条件等待队列中的第一个线程
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
//如果队列中的元素从满到非满,通知put线程
if (c == capacity)
signalNotFull();
return x;
}
3.5 其他方法
其他方法都类似于读写方法的原理,所以不再赘述。有兴趣的可以去看看源码。由于一般我们用阻塞队列都是用于多线程并发场景下,所以其实只要了解出队入队操作就基本上可以,其他方法很少用到,也不建议花太多时间去研究。
总结
- 元素不允许为空,为空会抛NullPointException异常;
- 可以设置队列长度,也可以是无界队列,对大值为Integer.MAX_VALUE;
- 基于链表,底层数据结构为链表,初始化后,对头会有一个空元素;
- 读写均加锁,所以安全类;
- 读写两把锁,所以读写可以同时进行,相对ArrayBlockingQueue来说吞吐量会大一点;
- 每次入队操作,都会初始化一个Node对象,所以频繁的入队会产生较多的新生代垃圾;