什么是阻塞队列
阻塞队列提供了两个阻塞行为的操作,当队列为空时,获取元素的线程会一直等待队列非空才会返回值,当队列满时,添加元素线程同理也会等待队列可用才执行添加。
阻塞队列常用于生产者和消费者场景,生产者负责添加元素,消费者负责获取元素而。
常用方法
方法
抛出异常
返回特殊值
阻塞
超时退出
插入
add(e)
offer(e)
put(e)
offer(e,time,unit)
移除
remove()
poll()
take()
poll(time,unit)
检查
element()
peek()
不可用
不可用
抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
原理及实现
关键属性
//主体
final Object[] items;
//出队列索引
int takeIndex;
//入队列索引
int putIndex;
//队列元素数量
int count;
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
复制代码
添加元素,添加到putIndex位置,同时putIndex+1,唤醒notEmpty上的移除元素队列。
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
复制代码
移除元素,消费takeIndex位置,同时takeIndex+1,唤醒moFull上的添加元素队列。
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
复制代码
添加元素,返回添加结果
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
//队列满直接返回
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
复制代码
移除元素,返回结果
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
复制代码
添加元素,添加失败,返回IllegalStateException异常
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
复制代码
移除元素,移除失败,返回NoSuchElementException异常
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
复制代码
阻塞添加元素,如果队列满了,则加入notFull队列,线程休眠等待有移除元素时唤醒
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
复制代码
阻塞的移除元素,如果队列为空,则加入notEmpty队列,线程休眠等待加入元素时候唤醒
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
复制代码
可设置等待时间的移除元素,设置线程等待时间。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
复制代码
同理上
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
复制代码
阅读代码可以发现,ArrayBlockQueue其实质是维护了一个有长度的数组,维护插入及移除两个节点,形成了一个环形的存储结构。
以上内容仅是自己学习笔记,如果错误欢迎指正