前言
七种阻塞队列是哪几个呢?面试的时候,可以都回答上来吗
DelayQueue
ArrayBlockingQueue
LinkedBlockingDeque
LinkedTransferQueue
LinkedBlockingQueue
SynchronousQueue
PriorityBlockingQueue
DA BT LSP
大BT?老帅批?一般说到阻塞,大家会想到哪里
想啥呢,我说的是交通,你们想的一定是是网络拥塞吧,好了,正文开始
正文
阻塞队列的实现原理
以最具代表性的ArrayBlockingQueue为例,其他的原理都差不多,面试的时候面试官也不会要求你全说,毕竟他自己也未必能全说出来
当然要是遇到那样的面试官,在反问环节,就让他自己回答这个问题吧
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
阻塞队列和非阻塞队列最主要的区别是,阻塞队列采用Condition(底层是UnSafe的park方法)来实现等待,而非阻塞队列更多是采用CAS+轮询的方式
可以看到ArrayBlockingQueue中,使用了两个等待队列,一个是notFull,另一个是notEmpty,从注释中不难猜出
- nofFull:用于队列满时,阻塞生产线程
- notEmpty:用于队列空时,阻塞消费线程
接下来,从阻塞队列的两大功能开始分析
往队列里存入元素
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();
}
相比其他源码,阻塞队列的相对比较友好,毕竟没那么多
- 添加元素时,先上锁,如果队列已满,则返回false
- 队列未满,则添加元素,唤醒notEmpty中阻塞的消费线程
大爷久等了,翠花。。。咳咳,可以起来消费了
从队列中取出元素
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;
}
- 取元素的时候,同样也是先上锁,如果队列为空,则阻塞等待
- 队列不为空,则元素出队列,这时队列没满,则唤醒生产线程
小插曲
容易的啃完了,到了难啃的环节了
方法描述 | 抛出异常 | 返回特殊的值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入数据 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
获取并移除队列的头 | remove() | poll() | take() | poll(time,unit) |
获取但不移除队列的头 | element() | peek() | 不可用 | 不可用 |
变态面试题:Java阻塞队列BlockingQueue里add、offer、put,take、poll的区别
也不知道一开始是谁想出来的面试题,平时不总结的话,遇到这种很(fan)友(ren)好(lei)的面试题,一时就很难回答上来,毕竟add、offer和put难道不是同一个意思吗
我们简单记忆一下
- add和remove,平时集合经常遇到,应该不陌生,这两个是最朴素的,也就是没做容错,会抛异常
- offer和poll,emm…一个意思,但用的是高级词汇,肯定很高级,会返回特殊值且支持超时退出
- put竟然不是对应get,而是对应take,好吧,会阻塞(吐槽完还是得记一记)
- 最后的element和peek比较好理解,peek有瞥一眼的意思,element是初中词汇吧,比较朴素
offer和put的区别
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
对比一下就能发现,offer和put的区别在于队列满的时候,put会调用await进行阻塞挂起,而offer在没有超时时间的情况下会返回false,即特殊值
poll和take同理哦,这里就不贴出来了
七种阻塞队列
1. DelayQueue
优先级队列实现的无界阻塞队列
2. ArrayBlockingQueue
数组实现的有界阻塞队列
3. LinkedBlockingDeque
链表结构组成的双向阻塞队列
4. LinkedTransferQueue
链表结构组成的无界阻塞TransferQueue队列
5. LinkedBlockingQueue
链表实现的有界阻塞队列
6. SynchronousQueue
每个插入操作必须等待另一个线程的对应移除操作
7. PriorityBlockingQueue
支持优先级的无界队列
结语
每种阻塞队列都有对应的用途,但区别都不会太大,看过源码之后,理解起来也没那么难。阻塞队列平时工作中很少都用上,没说明白的就等我下次复盘的时候再补上吧,下次被面试官问道,记得跟他说dabt lsp