阻塞队列有多种实现,这里我们就先分析一下ArrayBlockingQueue,该队列获取数据、插入数据、检测数据的方式有多种,但是不同的方法会有不同的效果,比如插入数据时,如果队列已经满了,调用add方法插入会抛异常,但是是有put插入数据就会阻塞当前线程。具体的效果查看下面的图。
这里我们只是重点查看阻塞的方法,put和take两个方法,看他是怎么做到阻塞的。
1. 创建一个有界阻塞队列,长度为2
BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(2);
2. 分析队列的构造函数
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();
}
capacity:队列长度
fair:是否为公平锁,默认为非公平锁
items:用来保存数据的数组,使用其它队列有其它的实现方式,比如链表
lock:锁,用来操作当前队列的锁
notEmpty:用来存储队列中没有数据时阻塞的线程
notFull:用来存储队列没有空间时阻塞的线程
lock.newCondition()是创建一个队列,对于不可进行争夺锁的线程阻塞放入到该队列中,后续可将该队列所有线程全部唤醒,放入到AQS队列,详细说明可查看ReentrantLock源码分析。
这里创建了两个等待队列,就是用来存放没有空间的阻塞队列和没有数据的阻塞队列
3.put方法插入数据
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();
}
}
判断当前队列数据是否已经满了,如果满了则将当前线程进行阻塞,如果没满则进行设置值
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
这里有个细节就是如果添加数据的指针到达数组的最后,则从数组的开头重新开始,数据插入的位置是由putIndex指针来标记的
当插入数据之后,唤醒等待获取队列数据的线程组,notEmpty.signal();
4.take方法获取值
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
判断当前队列中是否存在值,如果没有值,则将当前线程阻塞加入到等待队列。如果有值则获取
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
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;
}
这里也有一个细节,当获取值是数组最后一位,将获取值的指针takeIndex设置到数组开头。最后会唤醒等待添加数据的线程组:notFull.signal();