一:简述
今天和大家一起聊一聊阻塞队列,理解阻塞队列的原理实现。
二:什么是阻塞队列
首先阻塞队列是一个队列的数据结构,支持先进先出,其次相对于普通的阻塞队列,阻塞队列拥有两个特性:
1.在队列为空的时候,线程从阻塞队列中获取数据的时候会被阻塞。
2.在队列满了的时候,线程向阻塞队列中添加数据的时候会被阻塞。
三:添加元素
BlockingQueue接口定义了add(),put(),offer()三个添加元素的方法。
add()方法
boolean add(E e);
向队列中添加元素,如果已经达到最大容量,那么抛出异常。
put()方法
void put(E e) throws InterruptedException;
向队列中添加元素,如果已经达到最大容量,那么会阻塞当前线程。
offer()方法
offer()方法有两个重载的方法。
boolean offer(E e, long timeout, TimeUnit unit) throw InterruptedException
向队列中添加元素,如果已经达到最大容量,那么阻塞当前线程,到达超时时间后添加成功 返回true,否则返回false。
boolean offer(E e);
向队列中添加元素,成功返回true,否则返回false。
四:获取元素
BlockingQueue接口定义了take()和poll()两个方法。
take()
E take() throws InterruptedException;
向队列中获取元素,如果队列为空,那么阻塞当前线程。
poll()
E poll(long timeout, TimeUnit unit) throws InterruptedException;
向队列中获取元素,如果队列为空,那么阻塞当前线程,如果到达超时时间获取到元素,那么就将元素返回,否则返回null。
五:源码分析
juc中提供了许多BlockingQueue的实现,例如ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronousQueue等。今天的源码分析部分就以ArrayBlockingQueue为例,对阻塞队列的原理进行分析。
ArrayBlockingQueue是基于数组的结构来实现的一个阻塞队列。
主要的成员变量:
/** The queued items */
final Object[] items;
//下次获取元素的索引
int takeIndex;
/** items index for next put, offer, or add */
//下次添加元素的索引
int putIndex;
//当前队列的元素个数
int count;
//ReentrantLock锁
final ReentrantLock lock;
//队列是否为空的condition 为空是await()
private final Condition notEmpty;
//队列是否满了的condition 满了的时候await()
private final Condition notFull;
接下来我们主要对add(),put(),offer(),take()和poll()这几个方法进行分析。
offer(E e)方法
offer(E e)方法不支持添加null元素,添加null直接抛出异常,如果队列已经达到了最大容量,那么返回false,否则调用enqueue()方法加入到队列中,并且返回true。
public boolean offer(E e) {
//检查元素是否为null 为null抛出异常
checkNotNull(e);
final ReentrantLock lock = this.lock;
//加锁 保证线程安全
lock.lock();
try {
//已经达到最大容量 返回false
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
//将元素放入到数组中
items[putIndex] = x;
//putIndex(下次存在元素的索引下标)+1,如果putIndex和数组长度一样,那么需要重置成0
if (++putIndex == items.length)
putIndex = 0;
//元素个数+1
count++;
//通过condition的signal()方法唤醒正在等待获取元素的线程
notEmpty.signal();
}
offer(E e, long timeout, TimeUnit unit)
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//检查添加的元素是否为null 为null的话抛出异常
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//加锁
lock.lockInterruptibly();
try {
//如果队列已经满了 那么调用condition的awaitNanos()方法阻塞线程指定时长
while (count == items.length) {
if (nanos <= 0)
return false;
//awaitNanos()返回的nanos小于等于0 代表已经超时 超时的话直接返回false
nanos = notFull.awaitNanos(nanos);
}
//没有超时被唤醒 说明已经有线程从队列中取了元素,队列不是满的了 所以可以向队列中添加元素了。
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
add(E e)
add()方法会调用offer()方法添加元素 ,成功返回true 否则抛出异常。
public boolean add(E e) {
//调用offer()方法添加元素 ,成功返回true 否则抛出异常
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
put(E e)
put()方法向队列中添加元素。如果队列满了,那么对调用condition的await()方法阻塞当前线程,直到其他线程调用signal()方法唤醒,如果队列不是满的,那么跳出while循环,调用enqueue()方法添加元素。
public void put(E e) throws InterruptedException {
//检查元素是否为null
checkNotNull(e);
final ReentrantLock lock = this.lock;
//加锁
lock.lockInterruptibly();
try {
//如果队列满了 就等待
while (count == items.length)
notFull.await();
//直到队列不是满的 才会调用enqueue()方法添加元素
enqueue(e);
} finally {
lock.unlock();
}
}
take() take()方法获取元素,如果队列为空,那么调用condition的await()方法等待,直到被其他线程唤醒,队列不为空的情况下跳出while循环,然后调用dequeue()删除元素。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//加锁
lock.lockInterruptibly();
try {
while (count == 0)
//队列为空 那么进行等待
notEmpty.await();
//直到队列不为空 调用dequeue()方法获取元素,并且删除元素
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];
//然后将数组下标位置的值设置为null,
items[takeIndex] = null;
//takeIndex(下次获取元素的数组下标) +1,如果takeIndex已经等于数组的长度,那么需要重置为0
if (++takeIndex == items.length)
takeIndex = 0;
//元素个数减少
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
poll(long timeout, TimeUnit unit)
pull()方法获取元素的时候,如果对了为空,那么会阻塞当前线程指定的时间,如果在超时时间内获取到了元素,就直接返回,否则返回null。
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)
//超时的话返回null
return null;
nanos = notEmpty.awaitNanos(nanos);
}
//没有超时,调用dequeue()方法获取元素并且删除元素
return dequeue()方法获取元素并且删除元素;
} finally {
lock.unlock();
}
}
六:总结
阻塞队列是基于ReentrantLock和condition来实现的,如果你了解ReentrantLock和condition的原理,那么阻塞队列这一块是非常容易理解的,想要了解condition原理可以看我的另外一篇文章。文章地址:condition实现原理