并发编程之阻塞队列

前言

七种阻塞队列是哪几个呢?面试的时候,可以都回答上来吗

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,从注释中不难猜出

  1. nofFull:用于队列满时,阻塞生产线程
  2. 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();
}

相比其他源码,阻塞队列的相对比较友好,毕竟没那么多

  1. 添加元素时,先上锁,如果队列已满,则返回false
  2. 队列未满,则添加元素,唤醒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;
}
  1. 取元素的时候,同样也是先上锁,如果队列为空,则阻塞等待
  2. 队列不为空,则元素出队列,这时队列没满,则唤醒生产线程

在这里插入图片描述

小插曲

容易的啃完了,到了难啃的环节了

方法描述抛出异常返回特殊的值一直阻塞超时退出
插入数据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难道不是同一个意思吗

我们简单记忆一下

  1. add和remove,平时集合经常遇到,应该不陌生,这两个是最朴素的,也就是没做容错,会抛异常
  2. offer和poll,emm…一个意思,但用的是高级词汇,肯定很高级,会返回特殊值且支持超时退出
  3. put竟然不是对应get,而是对应take,好吧,会阻塞(吐槽完还是得记一记)
  4. 最后的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
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值