在上一节,介绍了ArrayBlockingQueue的添加元素的方法,本节,结合源码给大家介绍一下获取元素的方法。
获取元素的方法有下述几种
E poll() 立刻返回,如果队列为空,返回null
E take() 如果队列不为空,返回队首元素,否则阻塞到队列不为空
E poll(long timeout, TimeUnit unit) 等待timeout 时间的poll
E peek() 获得队首的元素,并不将这个元素弹出
分别看一下这几个方法的源码
1.E poll()
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
此方法先获得lock,如果lock获得成功,会先去判断count 的值,如果这个值为0,那就说明此队列为空,立刻返回null
2.E take()
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
take 方法和poll方法的区别就在于,如果count为0,那么在notEmpty上等待,直到被唤醒,这里有个需要遵循的原则,await/wait方法调用的时候,一定要确保本线程持有依赖的状态(这里边为count)的锁,并且被唤醒之后,依旧要去检查依赖条件的状态,这是《JAVA 并发编程实战》推荐的使用方式,原因此处不再多说。
3.E poll(long timeout, TimeUnit unit)
我们看到此方法传递了一个超时时间,也就是在条件队列上等待相应的时间,如果等待超过这个时间,那就返回空。
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 E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
5. 再来分析一下出队的方法,这个方法是几个获取方法的核心
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条件等待的线程,最后操作一下迭代器
6.remove ,
/**
* Removes a single instance of the specified element from this queue,
* if it is present. More formally, removes an element {@code e} such
* that {@code o.equals(e)}, if this queue contains one or more such
* elements.
* Returns {@code true} if this queue contained the specified element
* (or equivalently, if this queue changed as a result of the call).
*
* Removal of interior elements in circular array based queues
* is an intrinsically slow and disruptive operation, so should
* be undertaken only in exceptional circumstances, ideally
* only when the queue is known not to be accessible by other
* threads.
*
* @param o element to be removed from this queue, if present
* @return {@code true} if this queue changed as a result of the call
*/
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
我们再看removeAt这个方法,ArrayBlockingQueue支持删除任意位置上的元素
void removeAt(final int removeIndex) {
// assert lock.getHoldCount() == 1;
// assert items[removeIndex] != null;
// assert removeIndex >= 0 && removeIndex < items.length;
final Object[] items = this.items;
if (removeIndex == takeIndex) {
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// an "interior" remove
// slide over all others up through putIndex.
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
这个方法,首先判断了需要删除的元素是否是takeIndex所指向的元素,如果是,只需要将这个元素设置为null,并修改一下takeIndex 就好,但是如果不是,就需要将后面的元素全都往前移动一个位置,并且,迭代器也将固定位置的元素删除。
看到现在,貌似没有发现迭代器有什么明显的作用,暂且先认为他是这个ArrayBlockingQueue的一个链表实现的副本,等下节我们再研究