LinkedBlockingQueue 介绍
基于单向链表实现的有界阻塞队列,遵循先进先出(FIFO)原则。可用于实现一个生产者消费者模型,由于内部使用了 takeLock 和 putLock 两把锁,还有notEmpty 和 notFull 两个条件变量,因此它可以做到读与写并行操作。使用 AtomicInteger 记录队列中元素的个数。
核心变量介绍
/** 队列的容量,默认为 int 的最大值 */
private final int capacity;
/** 队列中元素个数 计数器 */
private final AtomicInteger count = new AtomicInteger();
/** 队列头节点 */
transient Node<E> head;
/** 队列尾节点 */
private transient Node<E> last;
/** 用于控制多线程取数据 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 控制消费者阻塞等待的条件 */
private final Condition notEmpty = takeLock.newCondition();
/** 控制多线程存数据 */
private final ReentrantLock putLock = new ReentrantLock();
/** 控制生产者阻塞等待的条件 */
private final Condition notFull = putLock.newCondition();
核心方法介绍
take方法
从队列头部取数据
1、先获取读锁 takeLock
2、判断队列是否为空
3、取数据
4、原子性减少元素数量
5、唤醒操作
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 可响应外部中断的方式获取锁
try {
while (count.get() == 0) {
notEmpty.await(); // 队列为空时,消费者阻塞等待生产者生产数据
}
x = dequeue(); // 从队列的头部取出数据,并将其从队列中移除
c = count.getAndDecrement(); // 队列中元素数量原子性减 1 ,并返回减一之前的原始大小值
if (c > 1)
notEmpty.signal(); // 队列不为空,唤醒其他消费者继续消费
} finally {
takeLock.unlock();
}
if (c == capacity) // 由于 c 是队列元素减一之前的原始大小值,从队列取出一个元素后,唤醒生产者继续生产数据
signalNotFull();
return x;
}
dequeue方法
private E dequeue() {
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // 将原始头节点的next 指向自己,将自己从队列中移除
head = first; // 设置新的头节点
E x = first.item;
first.item = null;// 因为队列的头节点是一个伪节点(不保存数据的节点),所以将item置空
return x;
}
signalNotFull 方法
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal(); // 唤醒生产者
} finally {
putLock.unlock();
}
}
put方法
向队列中插入数据
1、获取 putLock 锁,只有获取到锁才有操作队列的权限
2、判断队列是否已满,满了,阻塞等待
3、插入队列
4、插入成功后,如果队列没满,唤醒其它可能正在阻塞的生产线程
public void put(E e) throws InterruptedException {
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 {
while (count.get() == capacity) {
notFull.await(); // 队列满了,生产者阻塞
}
enqueue(node);// 插入队列
c = count.getAndIncrement();
// 当前队列未满,唤醒其它生产者继续生产数据
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 因为 count 默认值为0 , 执行到此步判断时,说明生产者已经将数据插入队列了,需要唤醒正在等待的消费者
if (c == 0)
signalNotEmpty();
}
/** 将 node 节点插入队列尾部 */
private void enqueue(Node<E> node) {
last = last.next = node;
}
remove方法
从队列中移除指定的元素
public boolean remove(Object o) {
if (o == null) return false;
fullyLock(); // 同时获取两把锁,性能会很低,尽量避免使用该操作
try {
// 从头遍历查找
for (Node<E> trail = head, p = trail.next; p != null; trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail); // 从队列中移除
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
void unlink(Node<E> p, Node<E> trail) {
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
if (count.getAndDecrement() == capacity)
notFull.signal();
}
drainTo方法
从队列中移除指定数向量的元素放到集合 c 中
public int drainTo(Collection<? super E> c, int maxElements) {
boolean signalNotFull = false;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
int n = Math.min(maxElements, count.get());
Node<E> h = head;
int i = 0;
try {
while (i < n) {
Node<E> p = h.next;
c.add(p.item);
p.item = null;
h.next = h;
h = p;
++i;
}
return n;
} finally {
if (i > 0) {
head = h;// 更新头节点
// 检测移动元素之前队列中元素是否已存满
signalNotFull = (count.getAndAdd(-i) == capacity);
}
}
} finally {
takeLock.unlock();
if (signalNotFull)
signalNotFull();// 根据上述条件决定是否唤醒生产者
}
}