Queue接口
在Queue接口中,除了继承Collection接口中定义的方法外,它还分别额外地定义插入、删除、查询这3个操作,其中每一个操作都以两种不同的形式存在,每一种形式都对应着一个方法。
- add方法在将一个元素插入到队列的尾部时,如果出现队列已经满了,那么就会抛出IllegalStateException,而使用offer方法时,如果队列满了,则添加失败,返回false,但并不会引发异常。
- remove方法是获取队列的头部元素并且删除,如果当队列为空时,那么就会抛出NoSuchElementException。而poll在队列为空时,则返回一个null。
- element方法是从队列中获取到队列的第一个元素,但不会删除,但是如果队列为空时,那么它就会抛出NoSuchElementException。peek方法与之类似,只是不会抛出异常,而是返回false。
BlockingQueue接口
BlockingQueue是JDK1.5出现的接口,它在原来的Queue接口基础上提供了更多的额外功能:当获取队列中的头部元素时,如果队列为空,那么它将会使执行线程处于等待状态;当添加一个元素到队列的尾部时,如果队列已经满了,那么它同样会使执行的线程处于等待状态。
BlockingQueue 阻塞队列,排队拥堵,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如下图所示:
- 线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素
- 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞
- 当阻塞队列是满时,从队列中添加元素的操作将会被阻塞
前面我们在说Queue接口时提到过,它针对于相同的操作提供了2种不同的形式,而BlockingQueue更夸张,针对于相同的操作提供了4种不同的形式。
该四种形式分别为:
- 抛出异常
- 返回一个特殊值(可能是null或者是false,取决于具体的操作)
- 阻塞当前执行直到其可以继续
- 当线程被挂起后,等待最大的时间,如果一旦超时,即使该操作依旧无法继续执行,线程也不会再继续等待下去。
- BlockingQueue中是不允许添加null的,该接受在声明的时候就要求所有的实现类在接收到一个null的时候,都应该抛出NullPointerException。
- BlockingQueue是线程安全的,因此它的所有和队列相关的方法都具有原子性。但是对于那么从Collection接口中继承而来的批量操作方法,比如addAll(Collection e)等方法,BlockingQueue的实现通常没有保证其具有原子性,因此我们在使用的BlockingQueue,应该尽可能地不去使用这些方法。
- BlockingQueue主要应用于生产者与消费者的模型中,其元素的添加和获取都是极具规律性的。但是对于remove(Object o)这样的方法,虽然BlockingQueue可以保证元素正确的删除,但是这样的操作会非常响应性能,因此我们在没有特殊的情况下,也应该避免使用这类方法。
ArrayBlockingQueue
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
/*
尝试获取锁,如果此时锁被其他线程锁占用,那么当前线程就处于Waiting的状态。
注意:当方法是支持线程中断响应的如果其他线程此时中断当前线程,那么当前线程就会抛出InterruptedException
*/
lock.lockInterruptibly();
try {
/*
如果此时队列中的元素个数为0,那么就让当前线程wait,并且释放锁。
注意:这里使用了while进行重复检查,是为了防止当前线程可能由于其他
未知的原因被唤醒。
(通常这种情况被称为"spurious wakeup")
*/
while (count == 0)
notEmpty.await();
//如果队列不为空,则从队列的头部取元素
return extract();
} finally {
//完成锁的释放
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
// 首先检查元素是否为空,否则抛出NullPointerException
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 进行锁的抢占
lock.lockInterruptibly();
try {
/*
* 当队列的长度等于数组的长度,此时说明队列已经满了,这里同样 使用了while来方式当前线程被"伪唤醒"。
*/
while (count == items.length)
// 则让当前线程处于等待状态
notFull.await();
// 一旦获取到锁并且队列还未满时,则执行insert操作。
insert(e);
} finally {
// 完成锁的释放
lock.unlock();
}
}
// 检查元素是否为空
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
// 该方法的逻辑非常简单
private void insert(E x) {
// 将当前元素设置到putIndex位置
items[putIndex] = x;
// 让putIndex++
putIndex = inc(putIndex);
// 将队列的大小加1
++count;
// 唤醒其他正在处于等待状态的线程
notEmpty.signal();
}