LinkedBlockingQueue
是基于单链表实现的阻塞队列
一、类属性
//内部类,单链表节点
static class Node<E> {
//节点值
E item;
//后驱节点
Node<E> next;
Node(E x) { item = x; }
}
//队列容量
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();
/**非空条件对列
队列空时调用notEmpty.await()阻塞
*/
private final Condition notEmpty = takeLock.newCondition();
/** 放锁 */
private final ReentrantLock putLock = new ReentrantLock();
/**非满条件队列
队列满时调用其await()方法阻塞
*/
private final Condition notFull = putLock.newCondition();
- 基于单链表实现
- 使用两个锁分别用于入队和出队,相比
ArrayBlockingQueue
效率会更高 - 多了
capacity
字段,因此可以确定我们可以指定队列是有界的,否则是无界的 - 两个条件队列跟
ArrayBlockingQueue
一样,生产者消费者模式,队列满时再放需要等待,队列空时再取也需要等待(当然有的方法可以不用等待)
二、构造函数
//默认构造,默认容量为无穷大,因此使用时最好手动指定大小
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* 指定容量大小,初始化首尾节点
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
/**
*指定初始集合
*/
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // 上锁
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
//遍历c入队
enqueue(new Node<E>(e));
++n;
}
//设置元素数量
count.set(n);
} finally {
//释放锁
putLock.unlock();
}
}
三、入队
3.1 put()
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();//put锁上锁
try {
/*
* 队列满时,调用非满条件队列阻塞
*/
while (count.get() == capacity) {
notFull.await();
}
//队列没满或有元素被取出唤醒后,将node加入队列
enqueue(node);
//原子增加元素数量
c = count.getAndIncrement();
if (c + 1 < capacity)//入队后没满
notFull.signal();//则唤醒在notFull队列中等待的一个线程
} finally {
//释放锁
putLock.unlock();
}
//如果队列之前是空的,这里入队后就要唤醒notEmpty条件队列
if (c == 0)
signalNotEmpty();
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();//加取锁
try {
//唤醒非满队列,告知可以取元素了
notEmpty.signal();
} finally {
//释放锁
takeLock.unlock();
}
}
//添加到队尾
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
-
加取锁
-
队列满,则调用notFull条件阻塞等待
-
否则入队,并原子增加count
-
如果入队后还没满,就再唤醒一个notFull等待中的线程,可以继续添加元素
-
释放取锁
-
如果队列添加元素前是空的,则唤醒notEmpty条件队列中的线程,告知可以取元素了
因此消费数据和添加数据(未满)都会唤醒一个notFull队列中的线程
LinkedBlockingQueue
的offer()
以及带超时的offer入队操作和ArrayBlockingQueue
原理基本一样,只是在入队后和上面put()
一样,如果队列未满,要再唤醒一个notFull
条件上的线程.
四、出队
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();
if (c > 1)
//如果减少后仍然没有空,就再唤醒一个阻塞在notEmpty中要取元素的队列,可以再取元素
notEmpty.signal();
} finally {
//释放取锁
takeLock.unlock();
}
//如果取之前,队列是满的,则唤醒在notFull条件上等待的队列
if (c == capacity)
signalNotFull();
return x;
}
//出队,返回队头元素
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
*/
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
- 加取锁
- 如果队列是空的,则调用notEmpty条件队列等待
- 如果队列非空,则出队一个元素,并更新元素个数
- 如果出队后队列仍然没空,则再唤醒一个等待出队的线程
- 释放取锁
- 如果出队前队列是满的,则唤醒一个在notFull中等待的线程。
流程很好理解,同put一样,这里也需要判断出队后,队列是否还是非空,继续唤醒一个在notEmpty条件等待的线程。
其他几个同ArrayBlockingQueue原理基本一样,也是多加了上面这个再唤醒的逻辑。
五、clear
public void clear() {
fullyLock();
try {
for (Node<E> p, h = head; (p = h.next) != null; h = p) {
h.next = h;
p.item = null;
}
head = last;
// assert head.item == null && head.next == null;
if (count.getAndSet(0) == capacity)
notFull.signal();
} finally {
fullyUnlock();
}
}
void fullyLock() {
putLock.lock();
takeLock.lock();
}
/**
* Unlocks to allow both puts and takes.
*/
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
在清除的时候,两把锁都使用
六、总结
- 基于单链表实现的有界队列
- 默认长度为
Integer.MAX_VALUE
,因此需要指定队列容量,否则消费元素速度比生产元素快时,会造成大量线程阻塞等待,发生OOM - 生产和消费元素都使用两把锁,putLock和takeLock,即锁分离机制,效率较高,避免像ArrayBlockingQueue只有一把锁,造成生产和消费互相阻塞
- clear时,两把锁都使用