BlockingQueue阻塞队列,它的阻塞体现在两个方面:
- 当队列为空时,出队操作将被阻塞;
- 当队列已经满的时候,进行入队将被阻塞;
BlockingQueue它继承自Queue接口,核心方法主要包括:
抛异常 | boolean结果 | 阻塞线程 | 阻塞/超时 | |
---|---|---|---|---|
入队 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
出队 | remove(o) | poll() | take() | poll(timeout, timeunit) |
获取元素(不出队) | element() | peek() |
上面四种元素操作分类都是在需要阻塞时,处置结果的不同:
抛异常:如果在入队时需要阻塞或者出队时需要阻塞的话,将会抛出:IllegalStateException异常;
boolean结果:如果操作需要需要阻塞的话,则直接返回false;
阻塞:如果入队和出队需要阻塞,则挂起当前线程;
阻塞/超时:在阻塞的基础上,允许添加阻塞的时间,超过阻塞时间,则返回false
BlockingQueue的实现类:
ArrayBlockingQueue:有界阻塞队列,内部基于数组实现;
LinkedBlockingQueue:
DelayQueue
PriorityBlockingQueue
SynchronousQueue
1. ArrayBlockingQueue
ArrayBlockingQueue继承自AbstractQueue并实现了BlockingQueue接口,它在创建的时候必须传入数组的长度,而不像ArrayList一样有自己的扩容机制;并且队列长度一但创建不允许再修改;
ArrayBlockingQueue依赖于ReentrantLock,所以它也提供公平和非公平的策略,默认是非公平模式;
1.1 属性和构造器
// 存放元素的数组
final Object[] items;
// 下一个出队的位置
int takeIndex;
// 下一个入队的位置
int putIndex;
// 当前队列中有多少元素
int count;
final ReentrantLock lock;
// 非空条件队列,如果队列为空时进行出队操作,则使用它进行阻塞
private final Condition notEmpty;
// 非满条件队列,如果队列满了继续入队时,则使用它进行阻塞
private final Condition notFull;
// 循环队列元素
transient Itrs itrs = null;
// 传入阻塞队列的长度,false是指公平和非公平模式
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
// 创建时,直接传递进来一个集合,将期添加到到阻塞队列中
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
// i++是后置+1,所以putIndex就是下一个要插入的位置
// 如果队列满了,则从头开始,因为头部元素可能出队,那么再入队时,需要判断count如果等待列队总长度,则说明队列满了
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
1.2 put & take
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 加可中断锁,因为ArrayBlockingQueue是线程安全的,所以会加锁
lock.lockInterruptibly();
try {
// 这里如果相等,表示队列已满,则使用notFull进行wait
while (count == items.length)
notFull.await();
// 否则,加入队列尾部
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
// 如果putIndex == items.length说明putIndex到了队列最尾部,则将putIndex=0,可以循环使用队列
if (++putIndex == items.length)
putIndex = 0;
count++;
// 添加进来了元素,则通知因获取元素而阻塞的线程
notEmpty.signal();
}
从源码中可以看出,它是一种阻塞式的put,如果要入队并且队列已满,则进行阻塞等待;
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 要出队,如果队列中没有元素,则通过notEmpty条件队列进行阻塞
// 这里是个while循环,为了防止被唤醒之后,资源又被别的线程抢走,会再次进行等待
while (count == 0)
notEmpty.await();
// 否则,出队
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
// takeIndex记录要出队元素的位置,将要出队的元素位置设置为null
items[takeIndex] = null;
// 如果takeIndex == items.length,则将takeIndex设置为0
if (++takeIndex == items.length)
takeIndex = 0;
// 元素数据减一
count--;
if (itrs != null)
itrs.elementDequeued();
// 通过因队列满,而无法入队的线程进行入队操作
notFull.signal();
return x;
}
对put和take有了了解之后,再看剩余的会非常轻松,具体代码不在这里列了;
2. LinkedBlockingQueue
LinkedBlockingQueue也是阻塞队列的一种实现,它是基于单向链表存储节点数据,它继承自AbstractQueue实现了BlockingQueue接口,所以上面提到的一些方法它都包含;
LinkedBlockingQueue在创建的时候,它需要传入链表的长度,如果不指定长度,则默认长度为Integer的最大值;
LinkedBlockingQueue采用两把锁,一把控制入队的putLock,一把控制出队的takeLock,入队和出队可以并发;
2.1 成员变量和构造方法
// 链表的总容量
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();
// 队列满时,继续put则阻塞
private final Condition notFull = putLock.newCondition();
// 不指定长度默认Integer最大值
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
// 初始化时,默认head和last指向一个空节点
last = head = new Node<E>(null);
}
public LinkedBlockingQueue(Collection<? extends E> c) {
// 队列长度 默认Integer最大值
this(Integer.MAX_VALUE);
// 向链表中添加元素使用putLock
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
// 队列满了则报错
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
// 向队列尾部添加节点
private void enqueue(Node<E> node) {
last = last.next = node;
}
2.2 put & take
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
// 将元素封装成Node
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
// 如果队列已满,则wait
while (count.get() == capacity) {
notFull.await();
}
// 否则,入队
enqueue(node);
// 如果队列未满,则唤醒因队列满而无法入队的线程
// getAndIncrement先获取未自增之前的值
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 如果c=0,则有可能存在出队操作被阻塞,而已添加入队元素,通过唤醒
if (c == 0)
signalNotEmpty();
}
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
private void enqueue(Node<E> node) {
last = last.next = node;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 如果c=0,表示队列为空了,唤醒
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
由于LinkedBlockingQueue采用两把锁来控制队头和队尾,所以添加元素之后,需要分别通知这两把阻塞锁;
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.signal();
} finally {
takeLock.unlock();
}
// 如果队列已经满了,并且本次已经出队一个元素,有一个空余位置,则唤醒入队线程
if (c == capacity)
signalNotFull();
return x;
}
private E dequeue() {
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();
}
}