开篇先解释一下队列:
数据结构分为线性数据和非先性数据
这里的线性数据结构指的是内存地址的线性存储,而下面要说的数组实现的队列就是线程地址存储结构中的一种,我们往下看
队列为先进先出的数据结构(FIFO),很多场景下我们会用到例如排队买票,消息队列,固定窗口付款,等等类似”供过于求“的场景。
我们这里以数组实现的案例,自己走一边流程,理解阻塞队列的奥秘
笔者模仿JDK的ArrayBlockQueue实现类类似的代码,因为要对逻辑进行分析,就只保留了核心代码如下:
package org.palm.hazelcast.laohanexcise;
import java.io.Serializable;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @date 19-9-18
* @auther jackliang
* @description TODO
*/
public class ArrayQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, Serializable {
private Object[] items;
/*类似C 语言的指针,如*putindex,这里可以意为指向要入库元素索引的指针,具体值为该指针地址所指向的值(item[putIndex])*/
int putIndex ;
/* 类似C 语言的指针,如*takeIndex,这里可以意为指向要出库元素索引的指针,具体值为该指针地址所指向的值(item[putIndex])*/
int takeIndex ;
int count ;
/*固定锁,锁住所有资源操作*/
final ReentrantLock lock;
/*出库操作条件锁 */
private final Condition notEmpty;
/*入库操作条件锁*/
private final Condition notFull;
public ArrayQueue(int maxSize, boolean fair) {
if (maxSize <= 0) {
throw new IllegalArgumentException();
}
items = new Object[maxSize];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
private static void checkNotNull(Object v) {
if (v == null) {
throw new NullPointerException();
}
}
@Override
public boolean add(E e) {
return super.add(e);
}
public ArrayQueue(int maxSize) {
this(maxSize, false);
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
@Override
public boolean hasNext() {
return false;
}
@Override
public E next() {
return null;
}
};
}
@Override
public int size() {
return count;
}
final E itemAt(int i) {
return (E) items[i];
}
@Override
public void put(E e) throws InterruptedException {
checkNotNull(e);
ReentrantLock lock = this.lock;
lock.lockInterruptibly();//可中断锁
try {
while (count == items.length) { //队列已满,锁住当前入库状态线程
notFull.await();//这里满足条件的suspend操作
}
} finally {
lock.unlock();
}
}
@Override
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
checkNotNull(e);
long time = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (count == items.length) {//队列已满,锁住当前入库状态线程
if (time < 0) {
/* 每一次notFull.awaitNanos(time)之后返回等待之后的剩余时间
A value less than or equal to zero
* indicates that no time remains.*/
return false;
}
time = notFull.awaitNanos(time);//这里等待指定时间
}
enqueue(e);//入队列操作
return true;
} finally {
lock.unlock();
}
}
@Override
public E take() throws InterruptedException {
ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
if (count == 0) {
notEmpty.wait();
}
E e = dequeue();
return e;
} finally {
lock.unlock();
}
}
@Override
public boolean contains(Object o) {
return super.contains(o);
}
private void enqueue(E e) {
Object[] items = this.items;
items[putIndex] = e;
if (++putIndex == items.length) {
/*这里有一个指针回环的操作,我们已初始化容量为3的队列为例,该指针加到2的时候, count为3,该指针指向第三个元素的地址
如果指针再加一,刚好满足上面的判断,那么指针从新指向第一个下标为0的索引地址
可以总结出来,count 的大小就是*putIndex和*takeIndex所指向下标索引之间相隔的距离*/
putIndex = 0;
}
count++;
//这里通知阻塞的一个消费者消费
notEmpty.signal();
}
private E dequeue() {
Object[] items = this.items;
E x = (E) items[takeIndex];
items[takeIndex] = null; // help GC
if (++takeIndex == items.length) {//跟随putIndex的脚步+1,这里很巧妙通过两个指针的移动实现了FIFO的效果
takeIndex = 0;
}
count--;
/*当count 减至0的时候输出输入两指针重合,这种回环操作很巧秒的实现不管中间删除和插入的操作多么切合,队列始终只需要保证
count == (putindex - takeIndex) 就可以实现FIFO的效果,这也是这个类设计的精华之所在*/
/*通知一个阻塞的生产者(take(),put(),add())生产*/
notFull.signal();
return x;
}
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
ReentrantLock lock = this.lock;
long nanos = unit.toNanos(timeout);
lock.lockInterruptibly();
try {
while (count == 0) {//这里为什么不用if要用while呢?可以尝试想想区别,应该好理解。
if (nanos <= 0) {
return null;
}
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
@Override
public int remainingCapacity() {
return items.length - count;
}
@Override
public int drainTo(Collection<? super E> c) {
return 0;
}
@Override
public int drainTo(Collection<? super E> c, int maxElements) {
return 0;
}
@Override
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length) { // is fulled
return false;
} else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
@Override
public E poll() {
ReentrantLock lock = this.lock;
lock.lock();
try {
return count == 0 ? null : dequeue();
} finally {
lock.unlock();
}
}
@Override
public E peek() {
ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex);
} finally {
lock.unlock();
}
}
public ArrayQueue(int maxSize, boolean fair, Collection<? extends E> c) {
this(maxSize, fair);
int i = 0;
final ReentrantLock lock = this.lock;
lock.lock();
try {
try {
for (E e : c) {
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
putIndex = i == maxSize ? 0 : i;
} finally {
lock.unlock();
}
}
}
重点就在与阻塞操作的处理和回环算法的指针处理FIFO逻辑,笔者表示膜拜。